15

I would like to generalize the following pattern:

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); }
};

The above approach does not scale with a number of increasing arguments. So, I'd like to do:

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); }
};

Is there a way to do it?

O'Neil
  • 3,790
  • 4
  • 16
  • 30
user3612643
  • 5,096
  • 7
  • 34
  • 55
  • 1
    the variadic sequence, can it contain the *same* type multiple times or will the sequence be unique types? – Nim May 23 '16 at 11:57

5 Answers5

12

another approach could be to have a check in bar to test if the type is in the sequence, else barf with a useful error message, this avoid any inheritance tricks..

#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
}
Nim
  • 33,299
  • 2
  • 62
  • 101
9

In case you don't actually need the bars and instead just need to constrain foo - we can use SFINAE to allow a call to it only with a type convertible to one of the As:

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&) { ... }
};

Where we can implement any with something like the bool_pack trick:

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>;
Barry
  • 286,269
  • 29
  • 621
  • 977
6
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

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • Nice, is there a way to get rid of one of the "bar"s? – user3612643 May 23 '16 at 11:32
  • Means "no"... I guess you need a terminal expansion point that is different from the intermediate ones? – user3612643 May 23 '16 at 11:36
  • @user3612643 whatever `using` refers to, it must exist. as I showed, you can have a single `bar` definition at the expense of additional template machinery. I can't think of any other solution at the moment. in the future there will be pack expansion allowed for `using` – Piotr Skotnicki May 23 '16 at 11:38
5
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); }
};

In a similar vain to Piotr Skotnicki's answer, this uses inheritance to build up a class with bar overloads for all of the template arguments. It's a bit cleaner though, with only one class template plus a partial specialization.

Community
  • 1
  • 1
Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
0
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 */ }
};
Mohammad Alaggan
  • 3,749
  • 1
  • 28
  • 20
  • That's going to declare multiple foo()s in addition to bar()s, which is not what was asked. Furthermore, due to inheritance, Foo<...>::bar() is going to resolve, by default, only to the topmost instance of bar(). – Sam Varshavchik May 23 '16 at 11:11
  • @SamVarshavchik Thank you for your comment. However, I am not sure how what you are saying happens. There is one function template `foor' an many instantiations of it, as far as I understand. – Mohammad Alaggan May 23 '16 at 11:13
  • 1
    @M.Alaggan Maybe it would help if you put an explanation of your code in your answer. – TartanLlama May 23 '16 at 11:18
  • 1
    overloaded functions must live in the same namespace. this doesn't hold for your code – Piotr Skotnicki May 23 '16 at 11:19