Ошибки с использованием decltype () и SFINAE - PullRequest
3 голосов
/ 09 ноября 2010

В ответ на какой-то другой вопрос где-то я написал этот код.

struct no_type{};
template<typename T> struct has_apply {
    static decltype(T().apply<0u>(double())) func( T* ptr );
    static no_type func( ... );
    static const bool result = !std::is_same<no_type, decltype(func(nullptr))>::value;
};

class A {
public:
    template< unsigned n >
    void apply( const double& );

};
class B {
};

int main()
{
  std::cout << std::boolalpha << has_apply< A >::result << '\n';
  std::cout << std::boolalpha << has_apply< B >::result << '\n';
  std::cin.get();
  return( 0 );
}

Теперь мне кажется, что результат должен быть истинным, если T предлагает нестатическую функцию-член «apply», которая принимает двойное значение и литерал параметра шаблона, и false в противном случае. Тем не менее, приведенный пример на самом деле не компилируется для класса B при компиляции has_apply<B>. Не должен ли факт, что замена T не удалась в выражении decltype, означать, что он просто вызывает другую функцию? Разве не в этом смысл СФИНАЕ?

Решено самым нелепым и бессмысленным образом:

struct no_type{};
template<typename T> struct has_apply {
    template<typename U> static decltype(U().apply<0u>(double())) func( U* );
    template<typename U> static no_type func( ... );
    static const bool result = !std::is_same<no_type, decltype(func<T>(nullptr))>::value;
};

class A {
public:
    template< unsigned n >
    void apply( const double& );

};
class B {
};

int main()
{
  std::cout << std::boolalpha << has_apply< A >::result << '\n';
  std::cout << std::boolalpha << has_apply< B >::result << '\n';
  std::cin.get();
  return( 0 );
}

Ответы [ 2 ]

3 голосов
/ 09 ноября 2010

SFINAE применяется, когда подстановка завершается неудачей для параметра шаблона шаблона функции, а не для параметра шаблона шаблона класса, который имеет (не шаблонную) функцию в качестве члена, как в вашем случае.

Послеисправляя это, вы должны по крайней мере изменить decltype(T().apply<0u>(double())) на decltype(T().template apply<0u>(double())), потому что выражение T() имеет зависимый тип.Причина этого заключается в следующем: когда компилятор впервые видит T().apply<0u>, он еще ничего не знает о T, так как он должен анализировать токены apply и < после .?apply может быть шаблоном члена, а затем < запустит список аргументов для него.Вместо этого OTOH apply может быть элементом, не являющимся шаблоном (например, элементом данных), и тогда < будет проанализирован как оператор «меньше чем».Существует двусмысленность, и пока компилятору еще рано разрешать эту проблему.Существует необходимость в механизме устранения неоднозначности, который программист мог бы использовать, чтобы сообщить компилятору, какой apply ожидается: шаблоном или нет.И тут на помощь приходит конструкция .template (или ->template, или ::template): если она присутствует, компилятор знает, что это должен быть элемент шаблона, в противном случае, если он отсутствует, то компилятор знает, что член не долженне может быть шаблоном.

Наконец, вот пример, который я создал, который работает правильно и дает желаемые результаты на g ++ 4.5.0 с -std=c++0x:

#include <iostream>

template < class T >
decltype( T().template apply< 0u >( double() ) ) f( T &t )
{
    return t.template apply< 0u >( 5. );
}

const char *f( ... )
{
    return "no apply<>";
}

class A {
public:
    template < unsigned >
    int apply( double d )
    {
        return d + 10.;
    }
};

class B {};

int main()
{
    A a;
    std::cout << f( a ) << std::endl;
    B b;
    std::cout << f( b ) << std::endl;
}

Вывод:

15
no apply<>

Теперь, если вы удалите оба .template из первого определения f(), то получится:

no apply<>
no apply<>

, который указывает на ошибку замещения для class A, поскольку он не 'у него есть не шаблонный элемент с именем apply.СФИНА в действии!

0 голосов
/ 14 ноября 2010

Извините за публикацию в качестве ответа, но комментарии кажутся мне двусмысленными.

Я видел людей, комментирующих declval (), но это не нужно. Вместо T () можно просто написать

 decltype( *(T*)0->template apply< 0u >( double() ) ) )

, который работает, так как он появляется только внутри decltype и фактически не оценивается во время выполнения. Другим вариантом было бы объявить

T T_obj( void );

и затем используйте

decltype( T_obj().template apply< 0u >( double() ) ) )

В качестве дополнительного комментария я вспоминаю, что читал, что Страуструп разработал язык, потому что он не хотел работать с неправильными инструментами. Разве C ++ не подходит для метапрограммирования?

Хотя c ++ 0x немного улучшает ситуацию, похоже, это не главное. Есть ли какой-нибудь другой язык, "близкий к металлу", как c ++, который предоставляет лучшие инструменты для написания метакода, который будет генерировать код во время компиляции?

...