SFINAE without void_t (maybe a template specialization question)

130 views Asked by At

Sorry for the title, I am not sure about the category of my question. I am trying to do an is_incrementable with SFINAE. This works fine, however when I try to understand it more deeply, and when I remove the void_t then the code snippet does not work as expected.

The original code:

#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<T&>() )>

       > : 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 evaluates to is_incrementable<int, void>::value which is the original template class and the specialization, too. In this case the compiler choses the specialized version, so value eguals to 1. For the string version, the specialization fails, SFINAE kicks in, so we have the base template only. (value equals to 0)

ii) When I change the code, and remove the void_t

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

template< typename T >
struct is_incrementable<T,
        decltype( ++std::declval<T&>() )    // 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 and 0 is printed. is_incrementable<int>::value means is_incrementable<int, void>::value, the specialization is is_incrementable<int, int>::value (I think), so we use the basic template. For the string the specialization fails anyway.

My question: iii) The funny part. If now I change the first line to use int as the default type

#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<T&>() )  // 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;
}

then again 0 and 0 are printed. Why? is_incrementable<int>::value means (I think) is_incrementable<int, int>::value and decltype( ++std::declval<T&>() ) should be int as well. So I think the compiler should use the specialized version. (and 1 should be printed)

If I remove decltype( ++std::declval<T&>() ) and write int then 1 and 1 printed (which are the expected print outs).

Could someone explain me what is happening in iii) please.

1

There are 1 answers

4
ruakh On BEST ANSWER

When T is int, decltype( ++std::declval<T&>() ) is int &, not int. So to get the output you were expecting, you would either change this:

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

to this:

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

or else change this:

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

to this:

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