如何使用类型特征来定义部分抽象的模板基类?

时间:2015-09-01 15:43:18

标签: c++ templates c++11 abstract-class typetraits

我正在研究以下想法:

存在一个通常具有多个模板参数的抽象模板化基类。该类定义了一个保证某些方法存在的合同,即方法getMax()。这种方法通常纯粹是虚拟的。除非存在特殊情况,其中可以给出合理的实现,而无需每次在派生类中手动实现它。所以基本上我想要实现的是(如果模板参数允许的话)(部分)实现抽象基类中已经存在的缩小方法。

我举了一个小例子来说明这个想法。 (注意:示例并不完美,例如std :: string的实现非常特殊,并且已经隐式强制TSize为std :: size_t)

#ifndef ABSTRACTBASE_H
#define ABSTRACTBASE_H

#include <type_traits>
#include <string>
#include <limits>

template <typename TSize, typename TVal1, typename TVal2>
class AbstractBase
{
    public:
        AbstractBase(){};
        virtual ~AbstractBase() {};
        virtual TSize getMax() const = 0; // <-- getMax should be generally 
                                          //     purely virtual.

    private:
        TVal1 value1;
        TVal2 value2;
};

//except when TVal1 is an arithmetic type in that case the following definition 
//shall become active.
template <typename TSize, 
          typename TVal1, 
          typename TVal2, 
          typename = typename std::enable_if<std::is_arithmetic<TVal1>::value, TSize>::type>
TSize AbstractBase<TSize, TVal1, TVal2>::getMax() const
{
    return std::numeric_limits<TVal1>::max();
}

//... or when TVal1 is a string where this defintion makes sense
template <typename TSize, 
          typename TVal1, 
          typename TVal2, 
          typename = typename std::enable_if<std::is_same<TVal1, std::string>::value, TSize>::type>
TSize AbstractBase<TSize, TVal1, TVal2>::getMax() const
{
    return value1.max_size();
}

//... in all other cases the getMax() method shall stay purely virtual and an 
//appropriate definition must be implemented inside a derived class

#endif //ABSTRACTBASE_H


#include "AbstractBase.h"
#include <string>
#include <iostream>

int main()
{
    AbstractBase<int, int, int> arthBase();
    AbstractBase<std::size_t, std::string, long> alphaBase();

    std::cout << arthBase.getMax() << std::endl;
    std::cout << alphaBase.getMax() << std::endl;
}

所以我想这里缺少的是一种实际上也将声明ov getMax()更改为虚拟的方法,尽管我不确定是否/如何使用type_traits这样做。

旁注:我还没有很好地处理类型特征。我知道它背后的SFINAE原理,它基本上表明如果替换模板参数失败,下面的代码将被排除在编译之外,而不是导致错误。如果负责启用/禁用方法的type_trait参数必须像我上面那样合并到类模板参数列表中,或者在单独的模板中提供类型特征参数是合法/可能的,我还没有发现参数列表。在这种情况下,我 guess 这是不可能的,因为enable_if语句测试必须在此上下文中声明/有效的类模板参数。

如果你使用真正复杂的类型特质魔法,我非常感谢你对该部分做出更精细的评论。

2 个答案:

答案 0 :(得分:1)

这里讨厌的部分是字符串版本需要访问成员变量。因此,解决这个问题的方法是将成员放在最基类中:

template <typename TVal1, typename TVal2>
class Members {
protected:
    TVal1 value1;
    TVal2 value2;
};

getMax()外包给其他类型:

// pure case
template <typename TSize, typename TVal1, typename TVal2>
struct VirtualMax : Members<TVal1, TVal2> {
    virtual TSize getMax() const = 0;
};

// integer case
template <typename TSize, typename TVal1, typename TVal2>
struct IntMax : Members<TVal1, TVal2> {
    TVal1 getMax() const { return std::numeric_limits<TVal1>::max(); }
};

// string case
template <typename TSize, typename TVal1, typename TVal2>
struct StringMax : Members<TVal1, TVal2> {
    size_t getMax() const {
        return this->value1.max_size();
    }
};

那么我们写一个类型特征:

template <typename TSize, typename TVal1, typename TVal2>
using Base_t = std::conditional_t<
    std::is_same<TVal1, std::string>::value,
    StringMax<TSize, TVal1, TVal2>,
    std::conditional_t<
        std::is_arithmetic<TVal1>::value,
        IntMax<TSize, TVal1, TVal2>,
        VirtualMax<TSize, TVal1, TVal2>
        >
    >;

然后使用该别名:

template <typename TSize, typename TVal1, typename TVal2>
class AbstractBase
: Base_t<TSize, TVal1, TVal2>
{ };

答案 1 :(得分:0)

After some tinkering and thanks to some ideas Barry gave me with his post above I took another shot at the problem myself. While Barrys answer provides a solution to the actual question I'd go and reply to myself to the question with "You don't.". The why Barry and me discussed in the comments:

  1. The design gets complex
  2. You clutter up your class hierarchy with lots of helper classes and structs that have no relevance for the actual project but exist purely out of architectural reasons to make the method selection based on type traits work.

So the much cleaner shot at this problem would be to keep the "AbstractBase" class purely abstract and combine type traits with inheritance deriving an ArithmeticBase class and a StringBase class using type traits to validate they only take valid template arguments. A solution could look like this:

#include <limits>
#include <string>
#include <type_traits>

#ifndef ABSTRACTBASE_H
#define ABSTRACTBASE_H

template <typename TSize, 
          typename TVal1, 
          typename TVal2>
class AbstractBase {
    public:
        virtual ~AbstractBase() {};
        virtual TSize getMax() const = 0;

    protected:
        TVal1 value1;
        TVal2 value2;
};

#endif //ABSTRACTBASE_H

#ifndef ARITHMETICBASE_H
#define ARITHMETICBASE_H

#include <limits>
#include <type_traits>
#include "AbstractBase.h"

template <typename TSize,
          typename TVal1,
          typename TVal2 = typename std::enable_if<std::is_arithmetic<TVal1>::value>::type>
class ArithmeticBase : public AbstractBase<TSize, TVal1, TVal2>
{
    public:
        virtual ~ArithmeticBase() {};
        virtual TSize getMax() const { return std::numeric_limits<TVal1>::max(); };
};

#endif //ARITHMETICBASE_H

#ifndef STRINGBASE_H
#define STRINGBASE_H

#include <limits>
#include <type_traits>
#include <string>
#include "AbstractBase.h"

template <typename TSize,
          typename TVal1,
          typename TVal2 = typename std::enable_if<std::is_same<TVal1, std::string>::value>::type>
class StringBase : public AbstractBase<TSize, TVal1, TVal2>
{
    public:
        virtual ~StringBase() {};
        virtual TSize getMax() const { return this->value1.max_size(); };
};

#endif //STRINGBASE_H

#include <string>
#include <iostream>
#include "ArithmeticBase.h"
#include "StringBase.h"
int main()
{
    ArithmeticBase<int, int, int> arthBase;
    StringBase<std::size_t, std::string, long> alphaBase;

    std::cout << arthBase.getMax() << std::endl;
    std::cout << alphaBase.getMax() << std::endl;
}