Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SFINAE and variadic template classes

I'm creating a class C that inherits from variable amount of classes. List of those classes is defined, for example: A,B. In function of class C I need to call functions from all base classes but objects can be C<A,B> , C<A>or C<B> so if I will call functions of class A in C<B> I will get an error. Here is example of the classes and how I've tried to solve problem:

    class A
    {
        int a;
    public:
        virtual void set_a(const int &value)
        {
            a = value;
        }
    protected:
        virtual int get_a()
        {
            return this->a;
        }
    };
    class B
    {
        int b;
    public:
        virtual void set_b(const int &value)
        {
            b = value;
        }
    protected:
        virtual int get_b()
        {
            return this->b;
        }
    };
    template<class ...T>
    struct Has_A
    {
        template<class U = C<T...>>
        static constexpr bool value = std::is_base_of < A, U > ::value;
    };

    template<class ...T>
    class C :
         virtual public T...
    {
    public:
    #define HAS_A Has_A<T...>::value

        void f()
        {
    #if HAS_A<>
            auto a = this->get_a();
    #endif
        auto b = this->get_b();
        cout << HAS_A<>;
    }
};

When I call f() of object C<A,B> it skips the call get_a() but output is true.

Initially, I wrote this

template<class U = C<T...>>
typename std::enable_if<!std::is_base_of<A, U>::value, int>::type get_a()
{
    return -1;
}
template<class U = C<T...>>
typename std::enable_if<std::is_base_of<A,U>::value, int>::type get_a()
{
    return A::get_a();
}

But I don't want to rewrite this for all functions of A and B. Let's assume that A has 10 more functions.

Is there any beautiful solution?

P.S Sorry for my English. I never used SFINAE before. Basically I have bunch of genes and I want to write convenient wrap for them where one can configure genes that he wants organism to have.

like image 553
Edwin Avatar asked Nov 24 '25 05:11

Edwin


2 Answers

In current standard, this is trivial:

void f() {
    if constexpr(Has_A<T...>::value) {
        auto a = get_a();
    }
    auto b = get_b();
}
like image 119
bipll Avatar answered Nov 25 '25 19:11

bipll


If you can use C++17, the bipll's solution (if constexpr ()) is (IMHO) the better one.

Otherwise, C++11 or C++14, I'm not sure it's a good idea but I propose the following solution because it seems to me funny (and a little perverted).

First of all, instead of Has_A I propose a more generic isTypeInList

template <typename...>
struct isTypeInList;

template <typename X>
struct isTypeInList<X> : public std::false_type
 { };

template <typename X, typename ... Ts>
struct isTypeInList<X, X, Ts...> : public std::true_type
 { };

template <typename X, typename T0, typename ... Ts>
struct isTypeInList<X, T0, Ts...> : public isTypeInList<X, Ts...>
 { };

I also propose the use of the simple indexSequence

template <std::size_t...>
struct indexSequence
 { };

that is inspired to std::index_sequence that (unfortunately) is available only starting from C++14.

So, inside C<T...>, you can define the template using

  template <typename X>
  using list = typename std::conditional<isTypeInList<X, Ts...>{},
                                         indexSequence<0u>,
                                         indexSequence<>>::type;

so that list<A> is indexSequence<0> if A is part of the T... variadic list, indexSequence<> (empty sequence) otherwise.

Now you can write f() that simply call an helper function f_helper() that receive as many indexSequences as many types you need to check.

By example: if you need to know if A and B are part of the T... variadic list, you have to write f() as follows

  void f ()
   { f_helper(list<A>{}, list<B>{}); }

Now f_helper() can be a private function and can be

  template <std::size_t ... As, std::size_t ... Bs>
  void f_helper (indexSequence<As...> const &,
                 indexSequence<Bs...> const &)
   {
     using unused = int[];

     int a { -1 };
     int b { -1 };

     (void)unused { 0, ((void)As, a = this->get_a())... };
     (void)unused { 0, ((void)Bs, b = this->get_b())... };

     // do something with a and b
   }

The idea is that As... is 0 if A is in T... or empty list otherwise.

So

int a { -1 };

initialize a with the value of your fake get_a().

With

(void)unused { 0, ((void)As, a = this->get_a())... };

is executed a = this->get_a(), only one time, iff (if and only if) A is in the T... variadic list.

The funny part of this solution is that a = this->get_a() isn't a problem when A isn't in the variadic list. Isn't there if As... is an empty list.

The following is a C++11 full working example (where I've renamed in Ts... the T... variadic sequence for C)

#include <utility>
#include <iostream>
#include <type_traits>

class A
 {
   private:
      int a;

   public:
      virtual void set_a (int const & value)
       { a = value; }

   protected:
      virtual int get_a ()
       { std::cout << "get_a()!" << std::endl; return this->a; }
 };

class B
 {
   private:
      int b;

   public:
      virtual void set_b (int const & value)
       { b = value; }

   protected:
      virtual int get_b ()
       { std::cout << "get_b()!" << std::endl; return this->b; }
 };

template <typename...>
struct isTypeInList;

template <typename X>
struct isTypeInList<X> : public std::false_type
 { };

template <typename X, typename ... Ts>
struct isTypeInList<X, X, Ts...> : public std::true_type
 { };

template <typename X, typename T0, typename ... Ts>
struct isTypeInList<X, T0, Ts...> : public isTypeInList<X, Ts...>
 { };

template <std::size_t...>
struct indexSequence
 { };

template <typename ... Ts>
class C : virtual public Ts...
 {
   private:
      template <typename X>
      using list = typename std::conditional<isTypeInList<X, Ts...>{},
                                             indexSequence<0u>,
                                             indexSequence<>>::type;

      template <std::size_t ... As, std::size_t ... Bs>
      void f_helper (indexSequence<As...> const &,
                     indexSequence<Bs...> const &)
       {
         using unused = int[];

         int a { -1 };
         int b { -1 };

         (void)unused { 0, ((void)As, a = this->get_a())... };
         (void)unused { 0, ((void)Bs, b = this->get_b())... };

         // do something with a and b
       }

   public:
      void f ()
       { f_helper(list<A>{}, list<B>{}); }
 };


int main()
 {
   C<>     c0;
   C<A>    ca;
   C<B>    cb;
   C<A, B> cab;

   std::cout << "--- c0.f()" << std::endl;
   c0.f();
   std::cout << "--- ca.f()" << std::endl;
   ca.f();
   std::cout << "--- cb.f()" << std::endl;
   cb.f();
   std::cout << "--- cab.f()" << std::endl;
   cab.f();
 }
like image 37
max66 Avatar answered Nov 25 '25 20:11

max66



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!