3

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.

Enlico
  • 23,259
  • 6
  • 48
  • 102
thamas
  • 183
  • 2
  • 9

1 Answers1

3

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 { };
ruakh
  • 175,680
  • 26
  • 273
  • 307
  • Thank you for your answer ruakh! You are right, both of the solution you mentioned works. One more thing, if I change the decltype line to "decltype( ++std::declval() )" (so I removed the &), then it prints 0 and 0 again, regardless of the first line ("typename = int" or "typename = int&") – thamas Mar 07 '21 at 12:01
  • 1
    @thamas, `++std::declval()` is ill-formed when coming from `is_incrementable::value`, i.e. with `T == int`. You can verify this by trying to compile `++std::declval();` vs `++std::declval();`. – Enlico Mar 07 '21 at 14:52
  • @thamas: To explain Enlico's comment: `++` requires an lvalue; that is, something like `++3` is not valid. – ruakh Mar 07 '21 at 18:49
  • Ok, so declval uses add_rvalue_reference, so using would return int&&, and that can not be incremented. For both of you, thank you very much! – thamas Mar 07 '21 at 19:33