Question

I would like to define a nullary static template member function which would be (explicitly) specialized on pointer-to-data-member and could have, for each specialization, different return type.

It should return some detailed information about each attribute, hence I will call this method trait. The trait object type returned will be inspected by other templates, so this whole machinery must be available at compile-time.

So far I have something like this (broken code, of course):

class Foo{
   // some data members
   int a; std::string b; int c;
   // how to declare generic template here?
   // compile-time error should ensue if none of the specializations below is matched

   // specialization for a (aTraitExpr is expanded from macro, so it is OK to repeat it)
   template auto trait<&Foo::a>()->decltype(aTraitExpr){ return aTraitExpr; }
   // specialization for b; the return type will be different than for trait<&Foo::a>
   template auto trait<&Foo::b>()->decltype(bTraitExpr){ return bTraitExpr; }
};

// some code which queries the trait at compile-time
// e.g. supposing all possible trait types declare isSerializable
// which happens to be True for a and False for b

Foo* foo;
template<bool isSerializable> void doSerialization(...);
template void doSerialization<true>(...){ ... };
template void doSerialization<false>(...){ /* no-op */ };

doSerialization<Foo::trait<&Foo::a>()::isSerializable>(...); // -> doSerialization<true>(foo)
doSerialization<Foo::trait<&Foo::b>()::isSerializable>(...); // -> doSerialization<False>(...)
doSerialization<Foo::trait<&Foo::c>()::isSerializable>(...); // -> compile error, specialization Foo::trait<&Foo::c> not defined

Could get some hint on how to achieve this? (I am not trying to invent a new serialization system, I already use boost::serialization; there will be more information in each trait, this is just for an example why it is needed at compile-time).

EDIT: I was able to get something nearing what I want, it is shown at ideone.com. I gave up having trait<Foo::a>() (for now), so there is static function getTrait_a() which returns reference to modifiable type-traits, which are however partially fixed at compile-time (so that Foo::TraitType_a::flags works, for instance). Thanks to everybody who replied, unfortunately I can only pick one of the answers as "answer".

Was it helpful?

Solution

It looks like you want several overloads instead of specializations. Unfortunately you don't detail on what xTraitExpr is, but it seems it's just a type that has a member isSerializable defined. I would probably go like this

class Foo {
   // your members have been omitted to save space...

   template<typename T, T Foo::*M>
   struct W { };

   static decltype(aTraitExpr) trait(W<int, &Foo::a>) {
     return aTraitExpr;
   }

   static decltype(bTraitExpr) trait(W<std::string, &Foo::b>) {
     return bTraitExpr;
   }

   // other overloads for other members...

public:
   // overloads for each member type
   template<int Foo::*M> 
   static decltype(trait(W<int, M>())) trait() { 
     return trait(W<int, M>()); 
   }

   template<std::string Foo::*M> 
   static decltype(trait(W<std::string, M>())) trait()  { 
     return trait(W<std::string, M>()); 
   }
};

trait(W<M>()) is a dependent call. A dependent call does ADL at definition and instantiation time and unqualified lookup only at definition time. That's why W and the additional trait overloads using it must be defined before the trait type overloads instead of after them, or the result of resolution in the return type and body of the functions will be different since they are parsed at different times (bodies are late parsed after the class definition, and return types are parsed immediately).

You can either make trait a constexpr function and make xTraitExpr be a literal class with a constexpr constructor initializing isSerializable appropriately, or you could apply decltype as follows

doSerialization<decltype(Foo::trait<&Foo::a>())::isSerializable>(...);

OTHER TIPS

I think it doesn't make sense to use a function template here. That being said, using a class template in its stead isn't that convenient either: you have to account for the fact that the non-static data members can have different types and there can be several non-static data members with the same type. Here's a possibility:

template<typename T>
struct is_serializable: std::false_type {};

struct Foo {
    int a; std::string b; int c;

    // Primary template left undefined on purpose
    // alternatively, could use a static_assert on a dependent
    // std::false_type::value for better diagnostics
    template<typename T, T t>
    struct attribute_trait;
};

// Define explicit specializations outside of class
template<>
struct Foo::attribute_trait<int Foo::*, &Foo::a>
: is_serializable<int> {};

template<>
struct Foo::attribute_trait<std::string Foo::*, &Foo::b>
: is_serializable<std::string> {};

Which is usable as

doSerialization<Foo::attribute_trait<decltype(&Foo::a), &Foo::a>::value>(/* stuff */);

The usual way to define a traits class is by wrapping a struct/class around a compile-time constant expression (and not by wrapping a function returning such an expression). The syntax to take a class member function is like this:

template
<
    SomeReturnType (SomeClass::*SomeMemberFunction)(SomeParameters)
>
class SomeTrait
{
    static const value = SomeCompileTimeConstantExpression;
};

In your case, you would do it like that:

template
<
    void (Foo::*f)()
>
class trait
{
    static const value = fTraitExpr;
};

You then specialize this trait for all member functions of class Foo:

template<>
class trait<&Foo::a>
{
    static const value = aTraitExpr;
};

// same for Foo::b and Foo::c

Furthermore, it is more idiomatic to overload function templates than to specialize them:

template<int V> struct Int2Type { enum { value = V }; };

Foo* foo;

template
<
    void (Foo::*f)()
>
void doSerialization(...)
{
    dispatch::doSerialization(Int2Type< trait<f>::value >(), ...);
}

namespace dispatch {

doSerialization(Int2Type< true >, ...) { ... };

doSerialization(Int2Type< false >, ...) { /* no-op */ };

} // namespace dispatch

You can then call it like this:

doSerialization<&Foo::a>(...);
doSerialization<&Foo::b>(...);
doSerialization<&Foo::c>(...);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top