Почему мы не находим правильную перегрузку оператора при использовании макроса?

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

template <auto F> struct Foo;

но мой компилятор (MSVC 2017.5) не поддерживает auto в списке параметров шаблона (хотя он поддерживает многие функции C++17). Итак, я написал взломать это как-то так:

template <typename T>
using Func = void (*)(T);

template <typename TF, TF F> struct Foo;
template <typename T, Func<T> F> 
struct Foo<Func<T>, F> { ... };

#define FOO(func) Foo<decltype(func), func>

Я реализовал потоковый оператор (для QDebug или любой другой текстовый поток), как это:

template <typename T, Func<T> F>
QDebug &operator <<(QDebug &out, const FOO(F) &foo)
{
    ...
    return out;
}

Но мой основной код не может найти право operator<< перегрузка:

void func(int) { ... }

...

FOO(&func) foo;
qDebug() << foo; // error

Удивительно, но все работает при определении оператора как

QDebug &operator <<(QDebug &out, const Foo<Func<T>, F> &foo)
//                                     ^^^^^^^^^^^^^^^

Кажется, что Func<T> из этой последней строки и decltype<F> из макроса не дают одинаковый тип. Но я проверил это и std::is_same_v<Func<int>, decltype(&func)> дает правду. Я не могу понять, почему с помощью макроса FOO дает мне любое другое поведение времени компиляции, как если бы я его не использовал.


Минимальный рабочий пример:

#include <iostream>

template <typename T>
using Func = void (*)(T);

template <typename TF, TF F> struct Foo;
template <typename T, Func<T> F> 
struct Foo<Func<T>, F> { };

#define FOO(func) Foo<decltype(func), func>

template <typename T, Func<T> F>
std::ostream &operator <<(std::ostream &out, const FOO(F) &foo)
// std::ostream &operator <<(std::ostream &out, const Foo<Func<T>,F> &foo)
{
    return out;
}

void func(int);

int main(int argc, char **argv)
{
    FOO(&func) foo;
    std::cout << foo << std::endl; // error
}

3 ответа

Решение

Как часть template auto на бумаге, мы также получили новое правило вывода в [temp.deduct.type]:

Когда значение аргумента соответствует не типу параметра шаблона P который объявлен с зависимым типом, выводится из выражения, параметры шаблона в типе P выводятся из типа значения.

Это правило позволяет следующему примеру работать в C++17, потому что мы можем вывести T от типа V:

template <typename T, T V>
struct constant { };

template <typename T, T V>
void foo(constant<decltype(V), V> ) { }

int main() {
    foo(constant<int, 4>{});
}

В C++14 и более ранних версиях этот пример плохо сформирован, потому что T не выводится Именно это поведение вы пытаетесь использовать (неявно) при использовании этого макроса, который расширяется до:

template <typename T, Func<T> F>
std::ostream &operator <<(std::ostream &out, const Foo<decltype(F), F> &foo);

Вы пытаетесь вывести T от F, Поскольку MSVC не поддерживает template auto тем не менее, это, возможно, неудивительно, что он также не поддерживает другие части машины, необходимые для template auto Работа. И в этом разница между макросом и немакро-альтернативой, которая может просто вывести T:

template <typename T, Func<T> F>
std::ostream &operator <<(std::ostream &out, const Foo<Func<T>,F> &foo)

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

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

template <typename T, Func<T> F> 
struct Foo<Func<T>, F> {
  friend QDebug &operator <<(QDebug &out, const Foo &foo){
    ...
    return out;
  }
};

Когда макрос не используется, второй параметр шаблона F может быть выведен из второго аргумента функции foo, Когда используется макрос, второй параметр шаблона F не может быть выведен из второго аргумента функции, потому что он появится внутри decltype: Foo<decltype(F), F> & foo, Ваш код может быть упрощен до

template<typename T>
void f(decltype(T) v){}

int v{};
f(v);

Компилятор знает тип аргумента (int) однако параметр шаблона T не может быть выведено из известного типа аргумента, потому что при использовании внутри decltypeT должны быть известны заранее.

Другие вопросы по тегам