Мета-итерация по аргументам шаблонов varadic
Я хотел бы обобщить следующий шаблон:
template<class A1, class A2, class A3>
class Foo {
protected:
template<class T>
void foo(const T& t) {...do stuff...}
public:
void bar(const A1& a) { foo(a); }
void bar(const A2& a) { foo(a); }
void bar(const A3& a) { foo(a); }
};
Приведенный выше подход не масштабируется с увеличением числа аргументов. Итак, я бы хотел:
template<class As...>
class Foo {
protected:
template<class T>
void foo(const t& a) {...do stuff...}
public:
for each type A in As declare:
void bar(const A& a) { foo(a); }
};
Есть ли способ сделать это?
Ответы
Ответ 1
Если вам действительно не нужен bar
, а просто нужно ограничить foo
- мы можем использовать SFINAE, чтобы разрешить ему вызов только с типом, конвертируемым в один из A
s:
template <class... As>
class Foo {
public:
template <class T,
class = std::enable_if_t<any<std::is_convertible<T, As>::value...>::value>>
void foo(T const&) { ... }
};
Где мы можем реализовать any
с чем-то вроде трюка bool_pack
:
template <bool... b> struct bool_pack { };
template <bool... b>
using any = std::integral_constant<bool,
!std::is_same<bool_pack<b..., false>, bool_pack<false, b...>>::value>;
Ответ 2
другой подход может заключаться в проверке bar
, чтобы проверить, находится ли тип в последовательности, иначе barf с полезным сообщением об ошибке, это позволяет избежать любых трюков наследования.
#include <iostream>
struct E {};
struct F {};
template <class... As>
class Foo
{
template <typename U>
static constexpr bool contains() {
return false;
}
template <typename U, typename B, typename ...S>
static constexpr bool contains() {
return (std::is_same<U, B>::value)? true : contains<U, S...>();
}
protected:
template <class T>
void foo(const T& a) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
public:
template <class T>
void bar(const T& a) {
static_assert(contains<T, As...>(), "Type does not exist");
foo(a);
}
};
int main()
{
Foo<E, F, E, F> f;
f.bar(F{});
f.bar(E{});
f.bar(1); // will hit static_assert
}
Ответ 3
template <class CRTP, class A, class... As>
struct FooBar
{
void bar(const A& a)
{
static_cast<CRTP*>(this)->foo(a);
}
};
template <class CRTP, class A, class B, class... As>
struct FooBar<CRTP, A, B, As...> : FooBar<CRTP, B, As...>
{
using FooBar<CRTP, B, As...>::bar;
void bar(const A& a)
{
static_cast<CRTP*>(this)->foo(a);
}
};
template <class... As>
class Foo : FooBar<Foo<As...>, As...>
{
template <class, class, class...>
friend struct FooBar;
protected:
template <class T>
void foo(const T& a) { }
public:
using FooBar<Foo, As...>::bar;
};
DEMO
Ответ 4
template <class A, class... As>
class Foo : public Foo<As...>
{
protected:
using Foo<As...>::foo;
public:
using Foo<As...>::bar;
void bar(const A& a) { foo(a); }
};
template <class A>
class Foo<A>
{
protected:
template <class T>
void foo(const T& t) { }
public:
void bar(const A& a) { foo(a); }
};
В подобном тщетном ответе Piotr Skotnicki это использует наследование для создания класса с перегрузками bar
для всех аргументов шаблона. Это немного более чистое, хотя, только с одним шаблоном класса плюс частичная специализация.
Ответ 5
template<class A, class Foo_t>
class bar_t {
public:
void bar(const A &a) { Foo_t::foo(a); }
};
template<class ...As>
class Foo : bar_t<As, Foo<As...> >... {
protected:
template<class T>
void foo(const T& a) { /* do stuff */ }
};