75

我阅读了Wikipedia 文章,其中介绍了 C++ 中用于执行静态(阅读:编译时)多态性的奇怪重复模板模式。我想对其进行概括,以便可以根据派生类型更改函数的返回类型。(这似乎应该是可能的,因为基类型知道模板参数的派生类型)。不幸的是,以下代码无法使用 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>'

4

5 回答 5

76

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所需的所有成员。

于 2011-05-15T05:16:29.580 回答
12

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

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;
}
于 2015-01-09T14:24:51.467 回答
11

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

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

这是有效的,因为返回类型的扣除base::foo被延迟到derived_t完成。

于 2016-08-12T13:30:30.110 回答
3

需要较少样板的类型特征的替代方法是将派生类嵌套在包含 typedef(或 using 的)的包装器类中,并将包装器作为模板参数传递给基类。

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);
}
于 2017-11-13T12:23:45.350 回答
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

于 2019-01-16T07:04:36.037 回答