Question

I'm starting to slowly fill my knowledge gap about C++ templates, and after reading a lot about how to handle errors before the compiler actually gets into the body of templated code, I came up with the following struct to check whether an object provides the interface that I need.

The desired interface is visible in the Model class.

#include <iostream>
#include <type_traits>

template <typename T>
struct is_model {
    private:
        template <typename B, typename A> struct size_t_allowed;
        template <typename B> struct size_t_allowed<B, size_t>{};

        template <typename B, typename A> struct double_allowed;
        template <typename B> struct double_allowed<B, double>{};

        template <typename Z> static auto test(const Z* z) -> decltype(
                size_t_allowed<size_t,decltype(z->getS())>(), 
                size_t_allowed<size_t,decltype(z->getA())>(),
                double_allowed<double,decltype(z->getTransitionProbability(0,0,0))>(),
                double_allowed<double,decltype(z->getExpectedReward(0,0,0))>(),
                std::true_type{} );
        template <typename> static auto test(...) -> std::false_type;
    public:
        enum { value = std::is_same<decltype(test<T>(0)), std::true_type>::value };
};

struct Model {
    size_t getS() const { return 0;}
    size_t getA() const { return 0;}

    double getTransitionProbability(size_t, size_t, size_t) const {return 0.0;}
    double getExpectedReward(size_t, size_t, size_t) const {return 0.0;}
};

template <typename M>
void algorithm(M, typename std::enable_if<is_model<M>::value>::type * = nullptr) {
    std::cout << "Algorithm has been performed.\n";
}

int main() {
    std::cout << is_model<int>::value << "\n";
    std::cout << (is_model<Model>::value ? "Yes" : "No" ) << "\n";
    Model m;
    algorithm(m);

    return 0;
}

My problems are the following:

  • g++ 4.8.1 compiles the code correctly, and prints all messages. clang++ 3.4 does not, in fact it returns false for is_model<Model>::value, and fails to compile algorithm(m). Which is right?
  • Currently I only found this (bad) way of checking the interfaces return types. Is it possible to make something better? I could not use a single template parameter for the structs *_allowed since otherwise the compiler would complain of a specialized struct within class scope.
  • Lastly, I was wondering how to perform checks on the function's parameters, and whether it even makes sense to do so.

EDIT: Thanks to Jarod's answer I have improved my solution. I still keep everything into a single class since I like the cleanliness of it. In addition I found the problem with clang: for some reason, in that particular circumstance it could not parse correctly std::true_type{} and neither would std::declval<std::true_type>() work. Substituting it with std::true_type() works. Still have got no clue, I even tried to reinstall the whole thing.

template <typename T>
struct is_model {
    private:
        template<typename U, U> struct helper{};

        template <typename Z> static auto test(Z* z) -> decltype(

          helper<size_t (Z::*)() const,                       &Z::getS>(),
          helper<size_t (Z::*)() const,                       &Z::getA>(),
          helper<double (Z::*)(size_t,size_t,size_t) const,   &Z::getTransitionProbability>(),
          helper<double (Z::*)(size_t,size_t,size_t) const,   &Z::getExpectedReward>(),

                                                     std::true_type());
        template <typename> static auto test(...) -> std::false_type;

    public:
        enum { value = std::is_same<decltype(test<T>((T*)nullptr)),std::true_type>::value };
};
Was it helpful?

Solution

Following may help, it checks complete signatures:

#include <cstdint>
#include <type_traits>

#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature)               \
    template <typename U>                                                   \
    class traitsName                                                        \
    {                                                                       \
    private:                                                                \
        template<typename T, T> struct helper;                              \
        template<typename T>                                                \
        static std::uint8_t check(helper<signature, &funcName>*);           \
        template<typename T> static std::uint16_t check(...);               \
    public:                                                                 \
        static                                                              \
        constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t); \
    }

DEFINE_HAS_SIGNATURE(has_getS, T::getS, size_t (T::*)() const);
DEFINE_HAS_SIGNATURE(has_getA, T::getA, size_t (T::*)() const);
DEFINE_HAS_SIGNATURE(has_getTransitionProbability, T::getTransitionProbability, double (T::*)(size_t, size_t, size_t) const);
DEFINE_HAS_SIGNATURE(has_getExpectedReward, T::getExpectedReward, double (T::*)(size_t, size_t, size_t) const);

template <typename T>
struct is_model :
    std::conditional<has_getS<T>::value
                              && has_getA<T>::value
                              && has_getTransitionProbability<T>::value
                              && has_getExpectedReward<T>::value,
                              std::true_type, std::false_type>::type
{};

Test it:

struct Model {
    size_t getS() const { return 0;}
    size_t getA() const { return 0;}

    double getTransitionProbability(size_t, size_t, size_t) const {return 0.0;}
    double getExpectedReward(size_t, size_t, size_t) const {return 0.0;}
};

static_assert(is_model<Model>::value, "it should respect contract");
static_assert(!is_model<int>::value, "it shouldn't respect contract");
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top