15

I wrote the following code:

#include <iostream>
#include <string>
#include <type_traits>

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

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

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

When I run it, I get 0 0. But I expected 0 1.

Any ideas?

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
ellipsis
  • 155
  • 1
  • 5
  • 4
    This is the place for [std::void_t](http://ideone.com/QUJxId). – DeiDei Jul 01 '17 at 07:22
  • Moreover, the docs for void_t have exactly this type of code as example, see http://en.cppreference.com/w/cpp/types/void_t code for `has_pre_increment_member` – stijn Jul 01 '17 at 07:24

1 Answers1

22

For std::string, the primary template is chosen and the specialization is considered. But decltype(++std::declval<T&>()) is ill-formed, so it is not considered and the primary template (non-specialized template) is used, which results in 0.

If you use int, it gets a bit more complicated. The primary template gets chosen by the compiler, as always, and then the specialization is considered (that's because specialization are always considered better matches). The specialization is <int, int&> for int, but it doesn't match the non-specialized template <int, void> (the void is the default template argument), so the specialization is ignored because it doesn't match.

So, the types of default template parameter have to match, or else the specialization is not taken into account, as a specialization is only taken when every template argument matches the specialization.

Just append a void() at the end to make the specialization match for the second template parameter, as the left expression is discarded and the type of void() is void, which matches the primary template's second template parameter.

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

In C++17, you would use std::void_t for that.

Community
  • 1
  • 1
Rakete1111
  • 47,013
  • 16
  • 123
  • 162
  • If I use `template struct is_incrementable : std::false_type {};` it also did not work. Can you explain this also? Thanks! – Klaus Jul 01 '17 at 07:36
  • 2
    @Klaus A non-overloaded pre increment operator returns the object itself, so `decltype(++i)` for `int i;` is a `int&` and not a `int`. If you use `typename = int&`, it will also print `0 1`. – Rakete1111 Jul 01 '17 at 07:41