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

recursive inheritance for overloading a method for a set of template arguments

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

所以,基本上我想为 class 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";
    }
};

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

error: redefinition of 'C' class C : public C

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

首先,class 不能从自身继承。

其次,您显然想要完成的是让每个模板参数生成一个 class 方法,该方法将 class 作为参数。

在这种情况下,像这样的东西应该可以工作。

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

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

请注意,您对此处讨论的 class 方法只有一个定义,而不是您尝试定义的两个。这是一个小改进。

另一个改进是考虑以这种方式重新排列 class 层次结构,前提是考虑到您的其他 class 要求:

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 *>,class 方法将始终被声明为 F<int> 的方法。这会稍微减少生成的代码浮点数,因为任何包含 intC 实例都会生成一个 class 方法实例,而不是两个单独的方法,例如 C<int, float>::f(const int &)C<int, char *>::f(const int &),否则它们将完全相同。

作为替代方法,我提出了一种基于混入的解决方案。请随意忽略 class 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> 在 compile-time 时被拒绝,更普遍的是所有那些包含相同类型两次或更多次的包。否则他们会产生 f(T) 的多个定义,随后的调用可能会模棱两可。

  • 您可以根据要求使用单个参数调用 f。无论如何,如示例所示,您可以使用 N 参数调用 f,该调用等效于使用单个参数 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 和网络上找到更多相关信息。