синтаксические шаблоны enable_if

Я использовал enable_if примерно таким образом с различными версиями GCC (до 5.2):

template< bool b, std::enable_if_t< b >... >
void fn() { std::cout << 1 << std::endl; }
template< bool b, std::enable_if_t< !b >... >
void fn() { std::cout << 2 << std::endl; }
// ...
fn< true >();
fn< false >();

Но, как оказалось, Clang 3.7 этого не приемлет ("вызов 'fn' неоднозначен").

Q1. Кто прав и почему?

Есть, конечно, и другие способы сделать это, но мне как-то не нравится

template< bool b >
std::enable_if_t< b, void > fa() { std::cout << 1 << std::endl; }
// ...

и тому подобное для того, чтобы сделать обычные части сигнатуры функции менее читаемыми, и

template< bool b, std::enable_if_t< b, int > = 0 >
void fd() { std::cout << 1 << std::endl; }
// ...

для включения нерелевантных элементов (типов и значений).

Вопрос 2. Какие другие (правильные, более читаемые, менее хакерские/странные) способы использования enable_if/enable_if_t существуют?


person vpozdyayev    schedule 01.12.2015    source источник
comment
Да, я тоже их не люблю, но, наверное, есть причина, по которой все так делают. Виноват С++.   -  person Lightness Races in Orbit    schedule 01.12.2015
comment
Как насчет template <bool b, class = std::enable_if_t<b>> ?   -  person Quentin    schedule 01.12.2015
comment
Более удобочитаемым способом являются концепции/ограничения, когда они становятся частью языка.   -  person chris    schedule 01.12.2015
comment
@Quentin Это ошибка обоих компиляторов.   -  person vpozdyayev    schedule 01.12.2015
comment
std::enable_if_t< b >... не имеет смысла. Вы объявляете пакет параметров шаблона типа void.   -  person T.C.    schedule 01.12.2015
comment
Связанный? stackoverflow.com/q/10377183   -  person dyp    schedule 01.12.2015
comment
@vpozdyayev Действительно. Сегодня для меня не ночь SFINAE!   -  person Quentin    schedule 01.12.2015
comment
@Т.С. Значит, это ошибка GCC? Тем не менее, использование std::enable_if_t< b, int >... не имеет значения.   -  person vpozdyayev    schedule 01.12.2015
comment
@dyp Похоже, спасибо.   -  person vpozdyayev    schedule 01.12.2015


Ответы (3)


В соответствии со стандартными параметрами 14.1/p7 Template [temp.param] (Emphasis Mine):

Нетиповый параметр шаблона не должен быть объявлен как имеющий тип с плавающей запятой, класс или тип void.

Следовательно, ваш фрагмент кода имеет неправильный формат. Таким образом, GCC ошибается в этом.

Однако, если вы измените на:

template< bool b, std::enable_if_t< b, int>... >
void fn() { std::cout << 1 << std::endl; }
template< bool b, std::enable_if_t< !b, int>... >
void fn() { std::cout << 2 << std::endl; }

Ограничение снято, и этот код является законным и должен быть принят. Судя по всему, кажется, что Clang отвергает и этот код. ИМХО, это баг Clang.

Насколько мне известно, о подобной ошибке сообщалось 23840.

Теперь о практической части, я не знаю, практично ли это/менее хакерски/менее странно, но вы можете сделать следующее:

template< bool b, std::enable_if_t< b, int> = 0 >
void fn() { std::cout << 1 << std::endl; }
template< bool b, std::enable_if_t< !b, int> = 0 >
void fn() { std::cout << 2 << std::endl; }
person 101010    schedule 01.12.2015
comment
Поэтому мы исправляем это, добавляя некоторый допустимый тип для enable_if, но clang по-прежнему жалуется: .com/a/870a4fe0ba9406ce ИМХО, ваш ответ неполный. - person dyp; 01.12.2015
comment
@dyp Код, который вы разместили, должен быть принят, я думаю, что это ошибка Clang llvm.org /bugs/show_bug.cgi?id=23840 - person 101010; 02.12.2015
comment
Похоже на llvm.org/bugs/show_bug.cgi?id=11723 Я косвенно упомянул в своем комментарии к ОП. - person dyp; 02.12.2015
comment
Не уверен насчет 14.1/7, поскольку мы имеем дело с нерасширенными пакетами параметров, а не с параметрами как таковыми (это). Это похоже на список типов ‹нижнего типа›, имеющий пустой список в качестве допустимого экземпляра, даже если нижний тип не имеет экземпляров. С другой стороны, у нас есть 14.6/8. Если каждая допустимая специализация.... С другой стороны (sic), этот пункт сам по себе кажется спорным, см., например, эта проблема. - person vpozdyayev; 02.12.2015
comment
Принимают независимо. Судя по всему, даже люди в std-discussion находят эти стандартные абзацы довольно сложными:/ - person vpozdyayev; 11.01.2016

Я не уверен, что вообще использовал бы здесь enable_if. Вы не пытаетесь ограничить набор перегрузок, поэтому я бы назвал это противоидоматическим.

Простая специализация работает нормально:

template <bool> void fn();
template <> void fn<true>() { std::cout << "true fn\n"; }
template <> void fn<false>() { std::cout << "false fn\n"; }
person Kerrek SB    schedule 01.12.2015
comment
Я не пытаюсь решить здесь какую-то конкретную проблему, кроме как использовать enable_if. Это ответ на другой вопрос. - person vpozdyayev; 02.12.2015

Q2. Какие другие (правильные, более читаемые, менее хакерские/странные) способы использования enable_if/enable_if_t существуют?

возможно, это более читабельно и менее хакерски?

#include <iostream>
#include <type_traits>


template< bool b >
auto fn() -> std::enable_if_t<b>
{
    std::cout << 1 << std::endl;
}

template< bool b>
auto fn() -> std::enable_if_t<!b>
{
    std::cout << 2 << std::endl;
}
// ...


auto main() -> int
{
    fn< true >();
    fn< false >();
    return 0;
}

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

#include <iostream>
#include <type_traits>

template <bool b> using When = std::enable_if_t<b, bool>;
template <bool b> using Unless = std::enable_if_t<!b, bool>;

template< bool b, When<b> = true>
void fn2()
{
    std::cout << 1 << std::endl;
}

template< bool b, Unless<b> = true>
void fn2()
{
    std::cout << 2 << std::endl;
}

auto main() -> int
{
    fn2< true >();
    fn2< false >();
    return 0;
}

... или, может быть, что-то подобное более выразительно?

template <bool b> using Eval = std::integral_constant<bool, b>;

template<bool b>
void fn3()
{
    struct fn3_impl
    {
        static void when(std::true_type)
        {
            std::cout << 1 << std::endl;
        }

        static void when(std::false_type)
        {
            std::cout << 2 << std::endl;
        }
    };

    fn3_impl::when(Eval<b>());
}
person Richard Hodges    schedule 01.12.2015