SFINAE和可变参数模板类

时间:2018-08-08 14:44:30

标签: c++ c++11 templates variadic-templates template-meta-programming

我正在创建一个class C,它继承自可变数量的类。定义了这些类的列表,例如:A,B。在class C的函数中,我需要从所有基类调用函数,但是对象可以是C<A,B>C<A>C<B>,所以如果我要调用class A的函数在C<B>中,我会收到一个错误消息。这是类的示例以及我如何解决问题的示例:

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

当我调用对象f()的{​​{1}}时,它会跳过调用C<A,B>,但输出为get_a()

最初,我写了这个

true

但是我不想为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(); } A的所有功能重写它。假设B还有10个功能。

有什么漂亮的解决方案吗?

P.S对不起,我的英语。我以前从未使用过SFINAE。 基本上,我有一堆基因,我想为它们写一个方便的包装,在那里人们可以配置他希望有机体拥有的基因。

3 个答案:

答案 0 :(得分:3)

在当前标准中,这很简单:

void f() {
    if constexpr(Has_A<T...>::value) {
        auto a = get_a();
    }
    auto b = get_b();
}

答案 1 :(得分:1)

我认为您可以使用function-member-pointer来做到这一点。

call_if_base仅在baseTT的基数的情况下才调用给定的函数指针。但是,所有函数结果都将被忽略,并且至少需要一个参数。

template <class baseT, class T, typename funcT, class ...Args>
typename std::enable_if<std::is_base_of<baseT, T>::value, void>::type call_if_base(T& obj, funcT func, Args... args) {
    (dynamic_cast<baseT&>(obj).*func)(args...);
}

template <class baseT, class T, typename funcT, class ...Args>
typename std::enable_if<!std::is_base_of<baseT, T>::value, void>::type call_if_base(T& obj, funcT func, Args... args) {

}   

template<class ...T>
class C :
     virtual public T...
{
public:
    void set(const int &value) {
        call_if_base<A, C>(*this, &A::set_a, 0);
        call_if_base<B, C>(*this, &B::set_b, 5);
    }
};

或作为成员函数

template<class ...T>
class C :
     virtual public T...
{       
public:
    void set(const int &value) {
        call_if_base<A>(&A::set_a, 0);
        call_if_base<B>(&B::set_b, 5);
    }
protected:
    template <class baseT, typename funcT, class ...Args>
    typename std::enable_if<std::is_base_of<baseT, C>::value, void>::type call_if_base(funcT func, Args... args) {
        (dynamic_cast<baseT&>(*this).*func)(args...);
    }

    template <class baseT, typename funcT, class ...Args>
    typename std::enable_if<!std::is_base_of<baseT, C>::value, void>::type call_if_base(funcT func, Args... args) {

    } 
};

答案 2 :(得分:1)

如果可以使用C ++ 17,则bipll的解决方案(if constexpr ())是(IMHO)更好的解决方案。

否则,对于C ++ 11或C ++ 14,我不确定这是一个好主意,但我提出以下解决方案,因为在我看来,这很可笑(而且有点变态)。

首先,我提出了一个更通用的Has_A

,而不是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...>
 { };

我还建议使用简单的indexSequence

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

启发std::index_sequence(不幸的是)仅从C ++ 14开始可用。

因此,在C<T...>内,您可以定义模板using

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

如果list<A>indexSequence<0>可变参数列表的一部分,则AT...,否则为indexSequence<>(空序列)。

现在,您可以编写f()来简单地调用一个辅助函数f_helper(),该函数会收到多达indexSequence个需要检查的类型。

举个例子:如果您需要知道AB是否属于T...可变参数列表的一部分,则必须按如下方式编写f()

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

现在f_helper()可以是private函数,并且可以是

  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
   }

这个想法是,如果As...0中,则AT...。否则为空列表。

所以

int a { -1 };

使用伪造的a的值初始化get_a()

使用

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

仅在a = this->get_a()可变列表中(如果且仅当A时才执行T...)。

此解决方案的有趣之处在于,当a = this->get_a()不在可变参数列表中时,A并不是问题。如果As...是一个空列表,则不存在。

以下是一个C ++ 11完整的工作示例(其中,我在Ts...中将T...的{​​{1}}可变序列重命名)

C