如何使用类型特征来定义部分抽象的模板基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 和我在评论中讨论的原因:
- 设计变得复杂
- 您的 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;
}
我正在研究以下想法:
存在一个具有多个模板参数的通用抽象模板化基础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 和我在评论中讨论的原因:
- 设计变得复杂
- 您的 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;
}