Реализация трейта дает разные результаты с clang и g++, что правильно?

Я начинаю постепенно восполнять пробел в своих знаниях о шаблонах C++, и, прочитав много о том, как обрабатывать ошибки до того, как компилятор действительно попадет в тело шаблонного кода, я придумал следующую структуру, чтобы проверить, предоставляет ли объект интерфейс, который мне нужен.

Нужный интерфейс виден в классе Model.

#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;
}

Мои проблемы следующие:

  • g++ 4.8.1 правильно компилирует код и выводит все сообщения. clang++ 3.4 этого не делает, на самом деле он возвращает false для is_model<Model>::value и не может скомпилировать algorithm(m). Какой правильный?
  • В настоящее время я нашел только этот (плохой) способ проверки типов возвращаемых интерфейсов. Можно ли сделать что-то лучше? Я не мог использовать один параметр шаблона для структур *_allowed, поскольку в противном случае компилятор жаловался бы на специализированную структуру в рамках класса.
  • Наконец, мне было интересно, как выполнять проверки параметров функции, и есть ли вообще смысл это делать.

РЕДАКТИРОВАТЬ: благодаря ответу Джарода я улучшил свое решение. Я все еще держу все в одном классе, так как мне нравится его чистота. Кроме того, я обнаружил проблему с clang: по какой-то причине в этом конкретном случае он не мог правильно разобрать std::true_type{} и не работал std::declval<std::true_type>(). Замена его на std::true_type() работает. До сих пор не понял, я даже пытался переустановить все это.

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 };
};

person Svalorzen    schedule 06.02.2014    source источник
comment
clang 3.5 отлично работает с вашим кодом coliru.stacked-crooked.com/a/614e54546a71c51b   -  person galop1n    schedule 06.02.2014
comment
мой clang 3.4 (из Debian) тоже работает нормально.   -  person ysdx    schedule 06.02.2014
comment
мой clang 3.4 (в Windows) тоже работает нормально.   -  person Jarod42    schedule 06.02.2014
comment
Я также попробовал clang на другом компьютере, и он работает, это должно быть что-то на моем (Lubuntu 13.10). Как вы думаете, я должен открыть отчет об ошибке?   -  person Svalorzen    schedule 07.02.2014


Ответы (1)


Следующее может помочь, он проверяет полные подписи:

#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
{};

Проверь это:

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");
person Jarod42    schedule 06.02.2014