Reborn
Reborn

Reputation: 145

Dectecting template methods with SFINAE

I have a simple trait struct hasMemberSerialize that I am trying to use to determine if any given class is compatible with callSerialize(). The struct looks like so:

template<typename Type, typename ArchiveType>
struct hasMemberSerialize {
    template<typename T, typename A>
    static auto test(int) -> decltype(Serialization::access::callSerialize(std::declval<A&>(), std::declval<T&>()), std::true_type);

    template<typename, typename>
    static std::false_type test(...);

    static const bool value = std::is_same<decltype(test<Type, ArchiveType>(0)), std::true_type>::value;
};

This compiles and runs fine, however, my hasMemberSerialize::value is always std::false_type. I've used a similar approach to check for non-template methods; however, the callSerialize() method I am checking looks something like:

template<typename Archive, typename Type>
static auto callSerialize(Archive& a, Type& t) -> decltype(t.serialize(a)) {
    // Implementation
}

I did some tests using std::cout like so:

Serialization::access::callSerialize(JSON, myType);

std::cout << std::boolalpha
    << hasMemberSerialize<MyType, JSONOutputArchive>::value << std::endl;

The method call callSerialize(JSON, myType) works as expected and serializes the type; however, hasMemberSerialize::value prints false. finally, myType is a simple test class:

class MyType {
    int myInt;

public:
    MyType() : myInt(4) {}

    template<typename Archive>
    void serialize(Archive& a) {
        a(myInt);
    }
};

...

MyType myType;

Upvotes: 1

Views: 81

Answers (2)

max66
max66

Reputation: 66200

As you have discovered, the problem was that you have to use std::true_type{}, with ending curly brackets, at the end of decltype()

So

decltype( <other elements>, std::true_type )

is wrong, and gives an error, where

decltype( <other elements>, std::true_type{} )
// .......................................^^

works.

The point is that decltype() return the type given an entity (a variable, a constant, etc.) or an expression of that type; so (by example) given decltype(3), you get int.

If you write

decltype( std::true_type )

you ask for the type of a type, and this is wrong.

If you write

decltype( std::true_type{} )

you ask for the type of an element (std::true_type{}) of type std::true_type; this is correct and you get std::true_type.

I suggest another way:

decltype( std::declval<std::true_type>() )

where std::declval() is a standard template function (only declared, but is enough for decltype() that return an element of the template type received.

So std::declval<std::true_type>() is an expression of type std::true_type and decltype() return, obviously, std::true_type.

In case of a type that is default constructible, you can create an entity of that type simply adding a couple of curly brackets at the end of the type name. But when a type isn't default constructible, you can't solve this why.

With std::declval() you get an expression of given type also when that type isn't default constructible.

In case of std::true_type you can solve in both way, but I suggest to use ever std::declval() anyway.

Upvotes: 0

Reborn
Reborn

Reputation: 145

I made a very simple mistake, the line

static auto test(int) -> decltype(Serialization::access::callSerialize(std::declval<A&>(), std::declval<T&>()), std::true_type);

needs to be

static auto test(int) -> decltype(Serialization::access::callSerialize(std::declval<A&>(), std::declval<T&>()), std::true_type{});

Notice: the curly brackets after std::true_type.

As Max66 explains in his comment:

the point is that decltype() return the type of an object; so decltype(3) is int; when you write decltype(std::true_type) (that is as decltype(int)) you ask the type of a type; you have to ask the type of an object of type std::true_type, that is decltype(std::true_type{}) or (better, IMHO) decltype(std::declval<std::true_type>())

Upvotes: 1

Related Questions