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

How to use type traits to define a partially abstract template base class?

我正在研究以下想法:

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

我做了一个小例子来说明这个想法。 (注意:该示例并不完美,例如 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() 更改为虚拟的方法,尽管我不太确定 if/how 这可以使用 type_traits.

旁注:我还没有太多地使用类型特征。我知道它背后的 SFINAE 原则,它基本上指出,如果模板参数的替换失败,则以下代码将被排除在编译之外而不是导致错误。我还没有发现负责 enabling/disabling 方法的 type_trait 参数是否必须像我上面那样合并到 class 模板参数列表中,或者如果它是 legal/possible 在单独的模板参数列表中提供类型特征参数。在这种情况下,我 猜测 这是不可能的,因为 enable_if 语句测试 classes 模板参数,在这种情况下必须是 declared/valid。

以防万一你使用了非常复杂的类型特征魔法,非常感谢对这部分的更详尽的评论。

这里令人讨厌的部分是字符串版本需要访问成员变量。所以解决这个问题的一种方法是将成员放在最基础的 class:

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

经过一些修补并感谢 Barry 在上面的 post 中给我的一些想法,我自己又一次尝试解决了这个问题。虽然 Barrys 的回答提供了实际问题的解决方案,但我会去用 "You don't." 回复自己的问题。 Barry 和我在评论中讨论的原因:

  1. 设计变得复杂
  2. 您的 class 层次结构因大量辅助 classes 和结构而变得杂乱无章,它们与实际项目无关,但纯粹出于架构原因而存在,以便根据类型进行方法选择特征有效。

因此,解决这个问题的更清晰的方法是保持 "AbstractBase" class 纯粹抽象,并将类型特征与派生 ArithmeticBase class 和 StringBase [=23] 的继承相结合=] 使用类型特征来验证它们只接受有效的模板参数。解决方案可能如下所示:

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