用于重载一组模板参数的方法的递归继承

时间:2017-02-19 00:19:04

标签: c++ inheritance recursion overloading variadic-templates

我需要找到一种方法,在给定一组模板参数的情况下递归构建一个类,以便该类继承自身并为模板参数列表中的当前第一个模板参数构建方法f,然后通过传递列表的其余部分继承自己。

所以,基本上我想为类C实现以下接口:

C<T1, T2, T3> c;

c现在有方法C::f(T1)C::f(T2)C::f(T3)

到目前为止我的方法是这样的:

// primary template
template <class H, class...>
class C {};

// base case where class... is empty
template <class H, class...>
class C<H>
{
public:
    void f(const H& h){
        // std::cout << typeid(h).name() << "\n";
    }
};

// recursive case where T is nonempty
template <class H, class... T>
class C : public C<T...>
{
public:
    void f(const H& h){
        // std::cout << typeid(h).name() << "\n";
    }
};

这实际上并没有编译,因为我得到了

  

错误:重新定义&#39; C&#39; C类:公共C

我的方法基本上是否可行,只是在语义上或语法上无效的代码问题,或者这种方法原则上不起作用?

2 个答案:

答案 0 :(得分:1)

对于初学者来说,一个班级不能从自身继承。

其次,您显然要完成的所有工作就是让每个模板参数生成一个类方法,将该类作为参数。

在这种情况下,这样的事情应该有效。

template<typename ...> class C;

template<>
class C<> {};

template<typename T, typename ...Args>
class C<T, Args...> : public C<Args...> {

public:

    void f (const T &)
    {
       // Whatever...
    }
};

请注意,这不是从自身继承的类。它是从另一个模板实例继承的模板实例。每个模板实例都是一个唯一的类。

请注意,您在这里有一个类方法的单一定义,而不是两个,正如您尝试的那样。这是一个小小的改进。

另一个改进是考虑以这种方式重新安排类层次结构,如果可以考虑到您的其他类要求而这样做:

template<typename T> class F {
public:

    void f (const T &)
    {
    }
};


template<typename ...> class C;

template<>
class C<> {};

template<typename T, typename ...Args>
class C<T, Args...> : public C<Args...> , public F<T> {

};

使用此方法,无论您使用C<int, float>还是C<int, char *>,都会将类方法声明为F<int>的方法。这会轻微减少生成的代码浮点数,因为任何包含C的{​​{1}}实例都会生成单个类方法实例,而不是像int这样的两个单独的方法C<int, float>::f(const int &),否则完全相同。

答案 1 :(得分:1)

作为一种替代方法,我提出了一种基于mixins的解决方案。请随意忽略类type_name引入的样板,其目的是向您展示在每个参数基础上选择正确的部分

它遵循一个最小的工作示例:

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

template<typename> struct type_name;
template<> struct type_name<int> { static const char *name; };
template<> struct type_name<double> { static const char *name; };
template<> struct type_name<char> { static const char *name; };

const char * type_name<int>::name = "int";
const char * type_name<double>::name = "double";
const char * type_name<char>::name = "char";

template<typename T>
struct Part {
    void func(T t) {
        std::cout << type_name<T>::name << ": " << t << std::endl;
    }
};

template<typename... T>
struct S: private Part<T>... {
    template<typename... Args>
    void f(Args&&... args) {
        using acc_t = int[];
        acc_t acc = { 0, (Part<std::decay_t<Args>>::func(std::forward<Args>(args)), 0)... };
        (void)acc;
    }
};

int main() {
    S<int, double, char> s;
    s.f(42);
    s.f(0.1);
    s.f('c');
    s.f('a', 0.3, 23);
}

此方法的一些优点:

  • Part<T>仅针对任何T定义一次,无论您在不同包中使用多少次。

  • S<T, T>在编译时被拒绝,并且通常包含两次或更多次包含相同类型的所有包。否则他们会生成f(T)的多个定义,随后的调用可能会有些模糊。

  • 您可以根据要求使用单个参数调用f。无论如何,如示例中所示,您可以使用f参数调用N,并且该调用相当于N使用单个参数调用f
    换句话说,你可以使用它:

    s.f('a', 0.3, 23);
    

    或者这个:

    s.f('a');
    s.f(0.3);
    s.f(23);
    

    两种情况下的结果都是相同的 如果需要,可以通过定义S来轻松关闭此功能:

    template<typename... T>
    struct S: private Part<T>... {
        template<typename U>
        void f(U &&u) {
            Part<std::decay_t<U>>::func(std::forward<U>(u));
        }
    };
    

wandbox上看到它正在运行。

作为旁注,这是用于在C ++ 14中模拟折叠表达式的常用技巧:

template<typename... Args>
void f(Args&&... args) {
    using acc_t = int[];
    acc_t acc = { 0, (Part<std::decay_t<Args>>::func(std::forward<Args>(args)), 0)... };
    (void)acc;
 }

您可以在SO和网络上找到更多相关信息。