Начиная с C++11 мы можем создавать шаблонные функции, которые могут принимать любую последовательность аргументов:
template <typename... Ts>
void func(Ts &&... ts) {
step_one(std::forward<Ts>(ts)...);
step_two(std::forward<Ts>(ts)...);
}
Однако предположим, что на самом деле имеет смысл вызывать мою функцию только в том случае, когда все аргументы имеют один и тот же тип — хотя любое количество аргументов будет в порядке.
Каков наилучший способ сделать это, то есть есть ли хороший способ ограничить шаблоны, чтобы в этом случае сделать красивое сообщение об ошибке, или, в идеале, исключить func
из участия в разрешении перегрузки, когда аргументы не совпадают?
Я могу сделать это действительно конкретным, если это поможет:
Предположим, у меня есть некоторая структура:
struct my_struct {
int foo;
double bar;
std::string baz;
};
Теперь я хочу иметь возможность делать такие вещи, как печатать элементы структуры для целей отладки, сериализовать и десериализовать структуру, последовательно посещать элементы структуры и т. д. У меня есть некоторый код, чтобы помочь с этим:
template <typename V>
void apply_visitor(V && v, my_struct & s) {
std::forward<V>(v)("foo", s.foo);
std::forward<V>(v)("bar", s.bar);
std::forward<V>(v)("baz", s.baz);
}
template <typename V>
void apply_visitor(V && v, const my_struct & s) {
std::forward<V>(v)("foo", s.foo);
std::forward<V>(v)("bar", s.bar);
std::forward<V>(v)("baz", s.baz);
}
template <typename V>
void apply_visitor(V && v, my_struct && s) {
std::forward<V>(v)("foo", std::move(s).foo);
std::forward<V>(v)("bar", std::move(s).bar);
std::forward<V>(v)("baz", std::move(s).baz);
}
(Выглядит немного утомительно генерировать такой код, но некоторое время назад я сделал небольшую библиотеку, чтобы помочь с этим.)
Итак, теперь я хотел бы расширить его, чтобы он мог посещать два экземпляра my_struct
одновременно. Использование этого заключается в том, что если я хочу реализовать операции равенства или сравнения. В документации boost::variant
они называют это "бинарным посещением" в отличие от "унарного посещения".
Вероятно, никто не захочет делать больше, чем бинарное посещение. Но предположим, я хочу сделать, например, общее n-ary
посещение. Тогда это выглядит так, я думаю
template <typename V, typename ... Ss>
void apply_visitor(V && v, Ss && ... ss) {
std::forward<V>(v)("foo", (std::forward<Ss>(ss).foo)...);
std::forward<V>(v)("bar", (std::forward<Ss>(ss).bar)...);
std::forward<V>(v)("baz", (std::forward<Ss>(ss).baz)...);
}
Но теперь все становится немного сложнее — если кто-то передает серию типов, которые вообще не являются одним и тем же структурным типом, код все равно может скомпилироваться и сделать что-то совершенно неожиданное для пользователя.
Думал сделать так:
template <typename V, typename ... Ss>
void apply_visitor(V && v, Ss && ... ss) {
auto foo_ptr = &my_struct::foo;
std::forward<V>(v)("foo", (std::forward<Ss>(ss).*foo_ptr)...);
auto bar_ptr = &my_struct::bar;
std::forward<V>(v)("bar", (std::forward<Ss>(ss).*bar_ptr)...);
auto baz_ptr = &my_struct::baz;
std::forward<V>(v)("baz", (std::forward<Ss>(ss).*baz_ptr)...);
}
Это, по крайней мере, вызовет ошибку компиляции, если они будут использовать его с несовпадающими типами. Но это также происходит слишком поздно - это происходит после разрешения типов шаблонов и, я думаю, после разрешения перегрузки.
Я думал об использовании SFINAE, например, вместо того, чтобы возвращать void, использовать std::enable_if_t
и проверять какое-то выражение std::is_same<std::remove_cv_t<std::remove_reference_t<...>>
для каждого типа в пакете параметров.
Но, во-первых, это выражение SFINAE довольно сложное, а во-вторых, у него есть недостаток — предположим, у кого-то есть производный класс struct my_other_struct : my_struct { ... }
, и он хочет использовать его с механизмом посетителей, поэтому некоторые параметры my_struct
, а некоторые my_other_struct
. В идеале система преобразовала бы все ссылки в my_struct
и применила посетителя таким образом, и, на самом деле, пример, который я привел выше с указателями членов foo_ptr
, bar_ptr
, baz_ptr
, сделал бы там правильную вещь, но мне даже не ясно, как написать такое ограничение с SFINAE - я должен попытаться найти общую базу для всех параметров, я думаю?
Есть ли хороший способ примирить эти опасения в целом?
forward<V>(v)
более одного раза. - person aschepler   schedule 14.08.2016&&
ref-qualifiedoperator()
. Поэтому я думаю, что проблема может возникнуть только в том случае, еслиoperator()
решит вызватьdelete this
из-за квалификаторов&&
или чего-то подобного. Я, конечно, хочу переслатьV
в отношенииconst
. Поэтому я думаю, что повторное перенаправлениеV
здесь нормально. Вы можете попытаться убедить меня в обратном. - person Chris Beck   schedule 14.08.2016