Проблема
Я написал сложный фрагмент кода шаблона, который можно скомпилировать с GCC 8.2.1 , но не с Clang 7.0 (код и ссылки на ошибки).
Я думаю, это может быть следствием этих вопросов и ответов , но я не могу его увидеть.
Мотивация
Я пишуКласс, который я хотел бы конструировать с двумя вызываемыми различными типами, но также с одним из них опущенным, то есть:
my_class(callable_1);
my_class(callable_2);
my_class(callable_1, callable_2);
Это должно пройти без проблем.Но почему бы не разрешить callable_1
и callable_2
быть шаблонами функций (или функторами с шаблоном operator()
).То есть я хотел бы иметь (или, по крайней мере, изначально хотел):
my_class([](auto arg) {});
my_class([](auto arg) {});
my_class([](auto arg) {}, [](auto arg) {});
Как вы можете видеть, оба вызываемых объекта, к сожалению, имеют одинаковую сигнатуру, поэтому нам нужно как-то различать их.Первый подход, о котором я мог подумать (и о котором идет речь в этом вопросе), - добавить параметр «tag» к одной из унарных перегрузок:
my_class([](auto arg) {});
my_class([](auto arg) {}, callable_2_tag());
my_class([](auto arg) {}, [](auto arg) {});
Это, на мой взгляд, приемлемо, но япришли к лучшим решениям:
- использовать тег (необязательно, если не неоднозначный) в сигнатуре второго вызываемого объекта (последний параметр или тип возвращаемого значения)
- сделать перегрузку второго конструктора вне-член или
static
функция-член с другим именем
Тем не менее, я хотел бы знать, почему существует разница в поведении между двумя компиляторами с моим первоначальным подходом и какойявляется правильным (или оба типа).
Код:
Для простоты я перевел перегрузки конструктора в обычные перегрузки функций my_class
.
#include <iostream>
#include <type_traits>
// parameter types for callbacks and the tag class
struct foo { void func1() {} };
struct bar { void func2() {} };
struct bar_tag {};
// callable checks
template <typename Func>
static constexpr bool is_valid_func_1_v = std::is_invocable_r_v<void, Func, foo>;
template <typename Func>
static constexpr bool is_valid_func_2_v = std::is_invocable_r_v<void, Func, bar>;
// default values
static constexpr auto default_func_1 = [](foo) {};
static constexpr auto default_func_2 = [](bar) {};
// accepting callable 1
template <typename Func1, std::enable_if_t<is_valid_func_1_v<Func1>>* = nullptr>
void my_class(Func1&& func_1)
{
my_class(std::forward<Func1>(func_1), default_func_2);
}
// accepting callable 1
template <typename Func2, std::enable_if_t<is_valid_func_2_v<Func2>>* = nullptr>
void my_class(Func2&& func_2, bar_tag)
{
my_class(default_func_1, std::forward<Func2>(func_2));
}
// accepting both
template <
typename Func1, typename Func2,
// disallow Func2 to be deduced as bar_tag
// (not even sure why this check did not work in conjunction with others,
// even with GCC)
std::enable_if_t<!std::is_same_v<Func2, bar_tag>>* = nullptr,
std::enable_if_t<is_valid_func_1_v<Func1> &&
is_valid_func_2_v<Func2>>* = nullptr>
void my_class(Func1&& func_1, Func2&& func_2)
{
std::forward<Func1>(func_1)(foo());
std::forward<Func2>(func_2)(bar());
}
int main()
{
my_class([](auto foo) { foo.func1(); });
my_class([](auto bar) { bar.func2(); }, bar_tag());
}
Для Clang это приведет к:
error: no member named 'func1' in 'bar'
my_class([](auto foo) { foo.func1(); });
~~~ ^
...
note: in instantiation of variable template specialization
'is_valid_func_2_v<(lambda at prog.cc:41:14)>' requested here
template <typename Func2, std::enable_if_t<is_valid_func_2_v<Func2>>* = nullptr>
^
Что здесь произошло? Ошибка замены - это ошибка?
Редактировать: Я совершенно не знал, что ошибка внутри предиката std::enable_if
также будет отключена... То есть не ошибка замещения.
Исправлено:
Если я поставлю SFINAE в качестве параметра функции, Clang хорошо с этим справится. Я не знаю, почему откладывание проверки от стадии вывода аргументов шаблона до стадии разрешения перегрузки имеет значение.
template <typename Func2>
void my_class(Func2&& func_2, bar_tag,
std::enable_if_t<is_valid_func_2_v<Func2>>* = nullptr)
{
my_class(default_func_1, std::forward<Func2>(func_2));
}
В общем, я погрузился в универсальностьвероятно, больше, чем я должен был иметь с моими знаниями, и теперь я плачу за это.Так что же мне не хватает?Внимательный читатель может заметить некоторые побочные вопросы, но я не хочу отвечать на все из них.Наконец, я прошу прощения, если можно было бы сделать намного более простой MCVE.