SFINAE без void_t (возможно, вопрос о специализации шаблона)

#c #sfinae #void-t

#c #sfinae #void-t

Вопрос:

Извините за название, я не уверен в категории моего вопроса. Я пытаюсь выполнить is_incrementable с SFINAE. Это работает нормально, однако, когда я пытаюсь понять это более глубоко, и когда я удаляю void_t, фрагмент кода работает не так, как ожидалось.

Исходный код:

 #include <iostream>

template< typename, typename = void >
struct is_incrementable : std::false_type { };

template< typename T >
struct is_incrementable<T,
          std::void_t<decltype(   std::declval<Tamp;>() )>

       > : std::true_type { };

int main()
{
    std::cout << is_incrementable<int>::value << std::endl;  // prints 1
    std::cout << is_incrementable<std::string>::value << std::endl;  // prints 0
    return 0;
}
 

i) is_incrementable<int>::value вычисляет, к is_incrementable<int, void>::value какому классу исходного шаблона относится и специализация. В этом случае компилятор выбирает специализированную версию, поэтому значение eguals равно 1.
Для строковой версии специализация завершается неудачно, запускается SFINAE, поэтому у нас есть только базовый шаблон. (значение равно 0)

ii) Когда я изменяю код и удаляю void_t

 template< typename, typename = void > 
struct is_incrementable : std::false_type { };

template< typename T >
struct is_incrementable<T,
        decltype(   std::declval<Tamp;>() )    // void_t is removed              
       > : std::true_type { };

int main()
{
    std::cout << is_incrementable<int>::value << std::endl;  // prints 0
    std::cout << is_incrementable<std::string>::value << std::endl;  // prints 0
    return 0;
}
 

печатаются 0 и 0.
is_incrementable<int>::value означает is_incrementable<int, void>::value , что специализация
is_incrementable<int, int>::value (Я думаю), поэтому мы используем базовый шаблон. Для строки специализация в любом случае завершается неудачно.

Мой вопрос: iii) Забавная часть. Если теперь я изменю первую строку, чтобы использовать int в качестве типа по умолчанию

 #include <iostream>

template< typename, typename = int >  // !!! int is used now
struct is_incrementable : std::false_type { };

template< typename T >
struct is_incrementable<T,
        decltype(   std::declval<Tamp;>() )  // void_t is removed 
       > : std::true_type { };

int main()
{
    std::cout << is_incrementable<int>::value << std::endl;  // prints 0
    std::cout << is_incrementable<std::string>::value << std::endl;  // prints 0
    return 0;
}
 

затем снова печатаются 0 и 0.
Почему?
is_incrementable<int>::value означает (я думаю) is_incrementable<int, int>::value
и
decltype( std::declval<Tamp;>() ) также должно быть int .
Поэтому я думаю, что компилятор должен использовать специализированную версию. (и 1 должен быть напечатан)

Если я удалю decltype( std::declval<Tamp;>() ) и запишу int , тогда будут напечатаны 1 и 1 (которые являются ожидаемыми распечатками).

Не мог бы кто-нибудь объяснить мне, что происходит в iii) пожалуйста.

Ответ №1:

Когда T есть int , decltype( std::declval<Tamp;>() ) есть int amp; , нет int . Таким образом, чтобы получить результат, который вы ожидали, вы должны либо изменить это:

 template< typename, typename = int >
struct is_incrementable : std::false_type { };
 

к этому:

 template< typename, typename = int amp; >
struct is_incrementable : std::false_type { };
 

или измените это:

 template< typename T >
struct is_incrementable<T,
        decltype(   std::declval<Tamp;>() )
       > : std::true_type { };
 

к этому:

 template< typename T >
struct is_incrementable<T,
        std::remove_reference_t<
          decltype(   std::declval<Tamp;>() )
        >
       > : std::true_type { };
 

Комментарии:

1. Спасибо за ваш ответ, руах! Вы правы, оба решения, о которых вы упомянули, работают. Еще одна вещь, если я изменю строку decltype на «decltype( std::declval<T>() )» (поэтому я удалил amp; ), тогда он снова напечатает 0 и 0, независимо от первой строки («typename = int» или «typename = intamp;»)

2. @thamas, std::declval<T>() неверно сформирован при исходе из is_incrementable<int>::value , т. Е. с T == int помощью . Вы можете убедиться в этом, попытавшись скомпилировать std::declval<intamp;>(); vs std::declval<int>(); .

3. @thamas: чтобы объяснить комментарий Энлико: требуется значение lvalue; то есть что-то вроде 3 недопустимо.

4. Хорошо, итак, declval использует add_rvalue_reference , поэтому использование <int> вернет intamp;amp; , и его нельзя увеличить. Вам обоим большое спасибо!