Почему распаковка параметров шаблона иногда не работает для std :: function? - PullRequest
5 голосов
/ 18 апреля 2020

Я столкнулся с проблемой. Когда я использую что-то вроде std::function<A(Fs...)>, это не работает, но std::function<A(Fs..., B)> работает. Это под Clang 8.0; все это не работает под G CC. Вот пример:

#include <functional>
template<typename A, typename B, typename ...Fs>
void func_tmpl1(std::function<A(Fs..., B)> callable)
{
}
template<typename A, typename ...Fs>
void func_tmpl2(std::function<A(Fs...)> callable)
{
}
class Cls1{};
void func0(std::function<void(float, Cls1)> callable)
{

}

int main()
{
    std::function<void(float, Cls1)> f1 = [](float a, Cls1 b){};
    func0(f1);
    func0([](float a, Cls1 b){});
    func_tmpl1<void, Cls1, float>(f1); // fails in GCC
    func_tmpl2<void, float, Cls1>(f1);

    func_tmpl1<void, Cls1, float>( // fails in GCC
        [](float a, Cls1 b)
        {

        }
    );
    func_tmpl2<void, float, Cls1>( // fails in both
        [](float a, Cls1 b)
        {}
    );

    return 0;
}

При Godbolt , мы видим, что G CC всегда терпит неудачу, но Clang терпит неудачу только при последнем вызове функции. Кто-нибудь может объяснить, что здесь происходит?

Ответы [ 2 ]

2 голосов
/ 18 апреля 2020

Для удобства давайте назовем три неудачных вызова в вашем коде # 1, # 2 и # 3.

Проблема в том, что когда аргументы шаблона, соответствующие пакету параметров шаблона, явно указаны, шаблон делает Пакет параметров по-прежнему участвует в выводе аргументов шаблона, и, если это происходит, сбой вывода делает весь вызов некорректным?

С [temp.arg.explicit] / 9 :

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

Мы можем сделать вывод, что аргумент шаблона вывод по-прежнему должен выполняться.

В объявлении func_tmpl1, std::function<A(Fs..., B)> - это не выводимый контекст ( [temp.deduct.type] / 9 : "Если шаблон список аргументов P содержит расширение пакета, которое не является последним аргументом шаблона, весь список аргументов шаблона представляет собой не выводимый контекст. "), поэтому templa вычет аргумента для Fs должен игнорироваться, и # 1 и # 2 являются правильными. Существует G CC отчет об ошибке .

Для # 3 вывод аргумента шаблона, очевидно, завершается неудачей (std::function<A(Fs...)> против лямбда-типа), но сбой вывода действительно делает код плохим -formed? На мой взгляд, стандарт по этому поводу неясен, и есть связанный вопрос . Судя по ответу CWG, # 3 действительно плохо сформирован.

1 голос
/ 18 апреля 2020

Это похоже на ошибку компилятора; компилятор пытается вывести аргументы шаблона, когда все аргументы уже указаны явно и, следовательно, вычитание не требуется. Или, возможно, ошибка связана с подстановкой, которая должна завершиться успешно.

В соответствии со стандартом, можно явно указать аргументы variadi c pack. См. Пример в [temp.arg.explicit] / 5 :

template<class ... Args> void f2();
void g() {
  f2<char, short, int, long>(); // OK
}

Когда все аргументы шаблона известны, предполагается, что компилятор просто создает экземпляр шаблон и покончим с этим; Разрешение перегрузки затем происходит в обычном режиме.

Чтобы обойти эту проблему, мы можем отключить вывод аргументов шаблона, введя не выводимый контекст. Например, вот так:

template<typename T> using no_deduce = typename std::common_type<T>::type;

template<typename A, typename B, typename ...Fs>
void func_tmpl1(no_deduce<std::function<A(Fs..., B)>> callable)
{
}

template<typename A, typename ...Fs>
void func_tmpl2(no_deduce<std::function<A(Fs...)>> callable)
{
}

(::type здесь является зависимым типом и становится недетерминированным контекстом)

Теперь он прекрасно компилируется в g++ и clang++ , ссылка на coliru


Сказав это, обратите внимание, что std::function в первую очередь предназначен для стирания типа и является дорогостоящей абстракцией поскольку он вызывает дополнительное косвенное обращение во время выполнения и является тяжелым объектом для передачи, поскольку он пытается сохранить копию любого возможного функтора, избегая при этом выделения кучи (что часто все еще происходит - тогда это большой пустой объект плюс выделение кучи).

Поскольку ваши функции уже являются шаблонами, вам действительно не нужно стирать тип; проще и эффективнее просто взять callable в качестве аргумента шаблона.

template<typename Func>
void func_tmpl(Func callable) // that's all
{
}

Или, если вам нужно дифференцировать по callable аргументам, можете использовать некоторые SFINAE:

#include <functional>
class Cls1{};

template<typename A, typename B, typename ...Fs, typename Func,
    typename = std::enable_if_t<std::is_invocable_r_v<A, Func, Fs..., B> > >
void func_tmpl1(Func callable)
{
}
template<typename A, typename B, typename ...Fs, typename Func,
    typename = std::enable_if_t<std::is_invocable_r_v<A, Func, B, Fs...> > >
void func_tmpl2(Func callable)
{
}
void func0(std::function<void(float, Cls1)> callable)
{
}

int main()
{
    std::function<void(float, Cls1)> f1 = [](float a, Cls1 b){};
    func0(f1); // func0 is not a template - so it requires type erasure
    func0([](float a, Cls1 b){});
    func_tmpl1<void, Cls1, float>(f1); // #1 OK
    func_tmpl2<void, float, Cls1>(f1); // #2 OK

    func_tmpl1<void, Cls1, float>([](float a, Cls1 b) {}); // #3 OK
    func_tmpl2<void, float, Cls1>([](float a, Cls1 b) {}); // #4 OK

    return 0;
}

ссылка на колиру

...