Является ли C ++ 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: 'unknown-type foo (F &&)': шаблон функции уже определен c ++ - scratch main.cpp 15

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


person Travis Gockel    schedule 28.01.2015    source источник
comment
Это, конечно, законно. Когда дело доходит до соответствия стандартам, у MSVC не самый лучший послужной список.   -  person 0x499602D2    schedule 29.01.2015
comment
MSVC еще не совместим с C ++ 11. Например, constexpr пока не работает.   -  person Pharap    schedule 29.01.2015
comment
Добавление неиспользуемого параметра , typename = void ко второму foo может привести к компиляции MSVC2015 Preview.   -  person jingyu9575    schedule 29.01.2015


Ответы (2)


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

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

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

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

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

Если такая сущность с именем D определена более чем в одной единице перевода, тогда

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

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

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


Обходной путь

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

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

Затем VC ++ компилирует его нормально. Я сократил цитату в [temp.over.link] / 6, в которой говорится об этом:

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

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

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())

Демо.

person Columbo    schedule 28.01.2015
comment
template<size_t> struct overload{enum type{}} тогда template<typename F, overload<0>::type...> не имеет ограничений на количество перегрузок, но, к сожалению, clang взрывается, когда его кормят. - person Yakk - Adam Nevraumont; 29.01.2015
comment
@Yakk Вместо этого я использовал тип указателя (потому что в настоящее время я не могу проверить преобразования, разрешенные для преобразованных константных выражений), но хорошая идея, вложенные типы, очевидно, уникальны. Типа пропустил это :) Как видите, я решил использовать аргумент по умолчанию вместо пакета из-за проблем в сочетании с другими пакетами. - person Columbo; 29.01.2015
comment
@Yakk Дальнейшее упрощение после того, как я понял разницу в парсинге между аргументами шаблона и списками параметров шаблона. - person Columbo; 29.01.2015
comment
Это мило! Даже используя =0 в нечестивых целях. Тем не менее, я надеюсь, что концепции lite сделают такой хакер ненужным, я должен проверить, покрывают ли они проблему. - person Yakk - Adam Nevraumont; 29.01.2015
comment
@Yakk Оказалось, что = 0 неверно, поскольку преобразования нулевого указателя в преобразованных константных выражениях могут преобразовываться только из nullptr_t. Фиксированный. - person Columbo; 02.04.2015

Эта функция называется «Expression SFINAE». Она еще не полностью поддерживается Visual C ++ ( см. «Функции C ++ 11/14/17 в VS 2015 Preview» для последнего обновления соответствия на момент этого ответа).

person James McNellis    schedule 28.01.2015
comment
Спасибо! По крайней мере, это обходное решение. Есть ли какие-нибудь сведения о том, будет ли это исправлено в большом выпуске? - person Travis Gockel; 29.01.2015