C ++静态多态性(CRTP)并使用派生类中的typedef


71

我阅读了Wikipedia上有关C ++中反复出现的模板模式以实现静态(阅读:编译时)多态性的文章。我想对其进行概括,以便可以基于派生类型更改函数的返回类型。(由于基本类型从template参数知道派生类型,因此这似乎应该可行)。不幸的是,以下代码无法使用MSVC 2010进行编译(我现在无法轻松访问gcc,因此我还没有尝试过)。有人知道为什么吗?

template <typename derived_t>
class base {
public:
    typedef typename derived_t::value_type value_type;
    value_type foo() {
        return static_cast<derived_t*>(this)->foo();
    }
};

template <typename T>
class derived : public base<derived<T> > {
public:
    typedef T value_type;
    value_type foo() {
        return T(); //return some T object (assumes T is default constructable)
    }
};

int main() {
    derived<int> a;
}

顺便说一句,我有一个使用额外模板参数的解决方法,但是我不喜欢它-当在继承链中传递许多类型时,它将变得非常冗长。

template <typename derived_t, typename value_type>
class base { ... };

template <typename T>
class derived : public base<derived<T>,T> { ... };

编辑:

MSVC 2010在这种情况下给出的错误消息是 error C2039: 'value_type' : is not a member of 'derived<T>'

g ++ 4.1.2(通过codepad.org)说error: no type named 'value_type' in 'class derived<int>'


大家知道,codepad.org可以为您编译和运行代码,我相信它使用gcc / g ++。因此,您永远不会超出g ++的能力范围:)
赛斯·卡内基

您能补充一下您遇到的错误吗,以便我对读者有用。
斯里拉姆·斯伯拉曼尼亚

@Seth:Ideone肯定使用gcc,所以又是一个:)
Matthieu M.

@Seth:感谢您提供有关codepad.org的提示!@Sriram:好的电话。我加了
塞缪尔·鲍威尔

Answers:


74

derived当您将其用作base其基类列表中的模板参数时,它是不完整的。

常见的解决方法是使用特征类模板。这是您的示例,特征化。这显示了如何通过特征使用派生类中的类型和函数。

// Declare a base_traits traits class template:
template <typename derived_t> 
struct base_traits;

// Define the base class that uses the traits:
template <typename derived_t> 
struct base { 
    typedef typename base_traits<derived_t>::value_type value_type;
    value_type base_foo() {
        return base_traits<derived_t>::call_foo(static_cast<derived_t*>(this));
    }
};

// Define the derived class; it can use the traits too:
template <typename T>
struct derived : base<derived<T> > { 
    typedef typename base_traits<derived>::value_type value_type;

    value_type derived_foo() { 
        return value_type(); 
    }
};

// Declare and define a base_traits specialization for derived:
template <typename T> 
struct base_traits<derived<T> > {
    typedef T value_type;

    static value_type call_foo(derived<T>* x) { 
        return x->derived_foo(); 
    }
};

您只需专门base_traits用于模板参数derived_t的任何类型,base并确保每个专门化都提供了所需的所有成员base


我创建了一个测试文件,并通过在Ubuntu g ++ 4.4.1中放入main()来编译OP的代码,效果很好。声明一个类似对象的对象Derived<int>给出错误,我试图找出原因?
iammilind

2
@iammilind那是因为在空的情况下main()不会发生模板实例化。仅当编译器尝试实例化和使用模板时才会出现某些错误。
greatwolf 2011年

1
对; 您必须实例化模板,以确保此处没有任何错误;如果int main() { derived<int>().base_foo(); }在我的特征示例的底部添加内容(这将强制实例化所有内容),则应使用Visual C ++ 2010,g ++ 4.5.1和最新的Clang构建进行编译。
James McNellis 2011年

2
您能否详细解释一下“当您将其用作基类列表中的基础模板变量时,派生不完整”的含义。它是什么方式不完整?
斯里拉姆·斯伯拉曼尼亚

1
@JamesMcNellis和Sriram,我觉得可能会造成混乱。稍微变平的版本(派生不是模板):struct derived : base<derived> {...};确实使base_traits没有定义。
mloskot 2011年

10

在C ++ 14中,您可以删除typedef和使用函数auto返回类型推导:

template <typename derived_t>
class base {
public:
    auto foo() {
        return static_cast<derived_t*>(this)->foo();
    }
};

之所以有效,是因为返回类型的推导base::foo被延迟到derived_t完成为止。


2
这种技术会限制返回值吗?它适用于字段或函数参数吗?C ++ 17会提供更多帮助吗?
cppBeginner

10

使用特征的一个小缺点是您必须为每个派生类声明一个。您可以像这样编写一个不太冗长和繁琐的解决方法:

template <template <typename> class Derived, typename T>
class base {
public:
    typedef T value_type;
    value_type foo() {
        return static_cast<Derived<T>*>(this)->foo();
    }
};

template <typename T>
class Derived : public base<Derived, T> {
public:
    typedef T value_type;
    value_type foo() {
        return T(); //return some T object (assumes T is default constructable)
    }
};

int main() {
    Derived<int> a;
}

这里的特点是什么?
sehe 2015年

1
这看起来很有趣,但我不太了解,您能再解释一下吗?
Ben Farmer

@BenFarmer而不是依靠派生类型在基类中提取value_type,我们将其直接传递给基类。现在,基类具有两个模板参数。如果只传递两种类型,这是非常实用的,但是如果传递,这将不方便。
巴蒂斯特·威希特

@BaptisteWicht确实,您可以使用可变参数模板和元组来提高灵活性。
matovitch

这不是多余的,因为您没有传递派生类的类型,而是将其模板作为基类的模板参数。(请注意,可以删除派生类中的typedef)
matovitch

3

另一种需要更少样板的类型特征的方法是,将派生类嵌套在一个保存有typedef(或使用)的包装器类中,并将包装器作为模板参数传递给基类。

template <typename Outer>
struct base {
    using derived = typename Outer::derived;
    using value_type = typename Outer::value_type;
    value_type base_func(int x) {
        return static_cast<derived *>(this)->derived_func(x); 
    }
};

// outer holds our typedefs, derived does the rest
template <typename T>
struct outer {
    using value_type = T;
    struct derived : public base<outer> { // outer is now complete
        value_type derived_func(int x) { return 5 * x; }
    };
};

// If you want you can give it a better name
template <typename T>
using NicerName = typename outer<T>::derived;

int main() {
    NicerName<long long> obj;
    return obj.base_func(5);
}

聪明,但这有副作用。例如,T将无法在功能模板中推论template<class T> f(outer<T>::derived x)(例如。(针对template<class T> f(derived2<T> x)
alfC

你可以写template <class C, class T = typename C::value_type> auto f(C x)。如果您担心接受任意类型,可以将其添加class = std::enable_if_t< std::is_same_v< C, NicerName<T> >, int>到模板参数列表中,但是它会变得很长。
Alkis

0

我知道这基本上是您发现并且不喜欢的解决方法,但是我想记录下来,并且还说它基本上是此问题的当前解决方案。

我一直在寻找一种实现此目的的方法,但从未找到好的解决方案。不可能的事实是最终之类的东西boost::iterator_facade<Self, different_type, value_type, ...>需要许多参数的原因。

当然,我们希望这样的工作:

template<class CRTP> 
struct incrementable{
    void operator++(){static_cast<CRTP&>(*this).increment();}
    using ptr_type = typename CRTP::value_type*; // doesn't work, A is incomplete
};

template<class T>
struct A : incrementable<A<T>>{
    void increment(){}
    using value_type = T;
    value_type f() const{return value_type{};}
};

int main(){A<double> a; ++a;}

如果有可能,则可以将派生类的所有特征隐式传递给基类。我发现获得相同效果的成语是将特征完全传递给基类。

template<class CRTP, class ValueType> 
struct incrementable{
    void operator++(){static_cast<CRTP&>(*this).increment();}
    using value_type = ValueType;
    using ptr_type = value_type*;
};

template<class T>
struct A : incrementable<A<T>, T>{
    void increment(){}
    typename A::value_type f() const{return typename A::value_type{};}
//    using value_type = typename A::value_type;
//    value_type f() const{return value_type{};}
};

int main(){A<double> a; ++a;}

https://godbolt.org/z/2G4w7d

缺点是派生类中的特征必须用限定符来访问typename或由启用using

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.