Подтвердить что ты не робот

Является ли "ленивый человек enable_if" законным С++?

Я часто использую технику, которую я называю "ленивый человек enable_if", где я использую decltype и оператор запятой, чтобы включить функцию, основанную на некотором вводе шаблона. Вот небольшой пример:

template <typename F>
auto foo(F&& f) -> decltype(f(0), void())
{
    std::cout << "1" << std::endl;
}

template <typename F>
auto foo(F&& f) -> decltype(f(0, 1), void())
{
    std::cout << "2" << std::endl;
}

С --std=c++11, g++ 4.7+ и Clang 3.5+ счастливо скомпилируют этот бит кода (и он работает так, как я ожидал). Однако при использовании MSVC 14 CTP5, я получаю эту ошибку с жалобой foo, которая уже определена:

Ошибка ошибки C2995: "шаблон foo (F &)" неизвестного типа ": уже определен С++ - scratch main.cpp 15

Итак, мой вопрос: является ли "ленивый человек enable_if" легальным С++ или это ошибка MSVC?

4b9b3361

Ответ 1

[temp.over.link]/6 указывает, когда две объявления шаблона функций являются перегрузками. Это делается путем определения эквивалентности двух шаблонов функций следующим образом:

Два шаблона функций эквивалентны, если они [..] имеют возвращаемые типы [..], которые эквивалентны с использованием правил описанных выше, для сравнения выражений с параметрами шаблона.

"Правила, описанные выше" - это

Рассматриваются два выражения, включающие параметры шаблона эквивалентно, если два определения функций, содержащие выражения, будут удовлетворять одному правилу определения (3.2) [..]

ODR, релевантный для этой части, указывает [basic.def.odr]/6, что

Учитывая такой объект с именем D, определенный более чем в одном переводе единица, затем

  • каждое определение D должно состоять из одной и той же последовательности токенов;

Очевидно, что поскольку типы возвращаемых данных (которые являются возвращаемыми типами возврата в [dcl.fct]/2), не состоят из те же токены, два определения функций, содержащие эти выражения, будут нарушать ODR.
Следовательно, объявления foo объявляют неэквивалентные шаблоны функций и перегружают имя.

Ошибка, которую вы видите, выдается из-за отсутствия поддержки со стороны VС++ для выражения SFINAE - предположительно, возвращаемые типы трейлинга не проверяются на эквивалентность.


Обход

Вы можете сделать шаблоны функций неэквивалентными по-другому. Измените список параметров шаблона. Если вы перепишете второе определение так:

template <typename F, int=0>
auto foo(F&& f) -> decltype(f(0, 1), void())
{
    std::cout << "2" << std::endl;
}

Затем VС++ компилирует его в порядке. Я сократил цитату в [temp.over.link]/6, которая охватывает это:

Два шаблона функций эквивалентны, если они объявлены в одном и том же scope, имеют одинаковое имя, имеют одинаковые списки параметров шаблона [..]

Фактически, чтобы иметь возможность легко вводить новые перегрузки, вы можете использовать небольшой помощник:

template <int I>
using overload = std::integral_constant<int, I>*;

Использование, например,

// Remember to separate > and = with whitespace
template <typename... F, overload<0> = nullptr>
auto foo(F&&... f) -> decltype(f(0, 1)..., void())

template <typename... F, overload<1> = nullptr>
auto foo(F&&... f) -> decltype(f(0, 1, 2)..., void())

Демо.