Почему идентификатор void_t <> не работает с gcc-4.9?

Рассмотрим следующий код:

#include <iostream>
#include <type_traits>

struct Test { Test& operator++(); };
struct NoIncrement { };

template <typename...> using void_t = void;

template <class, class=void_t<>>
struct has_pre_increment_member : std::false_type { };

template <class T>
struct has_pre_increment_member<T, void_t<decltype( ++std::declval<T&>() )>>
  : public std::true_type { };

int main() {
  std::cout << has_pre_increment_member<Test>::value << " ";
  std::cout << has_pre_increment_member<NoIncrement>::value << std::endl;
}

С версией g++ версии 5 и более поздней (и, конечно, флаг -std = С++ 14) этот код выводит

1 0

как и следовало ожидать. Вместе с g++ версии 4.9 (и флаг -std = С++ 14) он выдает

1 1

Оба утверждают, что используют один и тот же языковой стандарт, так что проблема здесь?

Ответы

Ответ 1

Это происходит в результате CWG Issue 1558 и теперь считается ошибкой в ​​gcc (в частности 64395 - исправлено в настоящее время). Идея этой проблемы заключается в том, что, поскольку вы фактически не используете параметры шаблона здесь:

template <typename...> using void_t = void;

нет ошибки замены, независимо от того, какие типы или выражения вы пытаетесь передать.

К счастью, существует легкое обходное решение, которое не связано с обновлением вашего компилятора. Мы можем переписать void_t, чтобы фактически использовать его пакет параметров, тем самым вызывая сбой замены:

namespace void_details {
    template <class... >
    struct make_void { using type = void; };
}

template <class... T> using void_t = typename void_details ::make_void<T...>::type;

Это сделает ваш пример правильным для всех версий gcc, которые я пробовал.