本文介绍一种简单的 C++ Concept实现:给模板参数加限制。
1.背景
#include
using namespace std;
typedef long long int64;
struct BigInteger {
BigInteger(int64 v): value(v) {}
int64 value;
};
template
BigInteger operator * (T v, const BigInteger& x) {
return BigInteger((int64)v * x.value);
}
struct Matrix {
};
int main() {
Matrix a;
BigInteger b(1);
a * b;
return 0;
}
这里使用BigInteger作为例子(并未真正实现),其中提供了operator *,期望支持和所有基础的int类型相乘(当然,有其它方法实现,为了和本文配合而使用了代码中的实现)。但是,有可能对于非int类型也匹配了该函数模板,进而导致错误:
concept.c: In instantiation of 'BigInteger operator*(T, const BigInteger&) [with T = Matrix]':
concept.c:21:7: required from here
concept.c:12:21: error: invalid cast from type 'Matrix' to type 'int64 {aka long long int}'
return BigInteger((int64)v * x.value);
然而,该错误并不是错误的根源所在,所以看上去有点莫名其妙。另外,根据BigInteger的设计,只应该匹配所有基础的int类型,而代码中并没有这样的保证,只能通过文档说明和使用者的小心翼翼来保证正确性。
2.实现
所以本文基于 SAFINAE,利用c++模板的一些技巧,在一定程度上对模板参数加以限制,避免这种情况出现。
#include
#include
using namespace std;
typedef long long int64;
template
struct CheckConditionThen{};
template<>
struct CheckConditionThen{
template
struct IdenticalType {
typedef U type;
};
};
#define REQUIRES(...) typename CheckConditionThen<__VA_ARGS__>::
#define RETURN(...) template IdenticalType<__VA_ARGS__>::type
struct BigInteger {
BigInteger(int64 v): value(v) {}
int64 value;
};
template
REQUIRES(is_integral::value)
RETURN(BigInteger)
operator * (T v, const BigInteger& x) {
return BigInteger((int64)v * x.value);
}
struct Matrix {
};
int main() {
Matrix a;
BigInteger b(1);
a * b;
return 0;
}
对于该代码,错误信息为:
concept1.c: In function 'int main()':
concept1.c:38:5: error: no match for 'operator*' (operand types are 'Matrix' and 'BigInteger')
a * b;
~~^~~
concept1.c:28:1: note: candidate: template typename CheckConditionThen::value>::IdenticalType::type operator*(T, const BigInteger&)
operator * (T v, const BigInteger& x) {
^~~~~~~~
concept1.c:28:1: note: template argument deduction/substitution failed:
concept1.c: In substitution of 'template typename CheckConditionThen::value>::IdenticalType::type operator*(T, const BigInteger&) [with T = Matrix]':
concept1.c:38:7: required from here
concept1.c:28:1: error: no class template named 'IdenticalType' in 'struct CheckConditionThen<0>'
从中可以看到,明确指出了是a * b导致的。
3.分析
将整个宏展开,可以看出首先利用type_trait对T进行限制,当条件满足时返回true,否则返回false。其次,将这个返回值放到某个类模板中,该类模板对于true值时拥有一个IdenticalType的类模板,而对于false值时没有。所以在条件不满足时,这里会出现错误,导致模板匹配失败。对于true值时,IdenticalType用来接收函数的返回类型再把该返回类型返回作为一个类型表达式。
该实现是为了配合REQUIRES 在前 RETURN在后的宏的使用,在逻辑结构上并不美妙。如果我们从数据流处理的角度来看:
input T => filter T => output T会更加优美:
template
struct IdenticalType {
typedef T type;
};
template
struct IdenticalValue {
enum {value = v};
};
template
struct CheckCondition {
};
template
struct CheckCondition {
typedef T type;
};
#define RETURN(...) typename CheckCondition::type,
#define WHEN(...) IdenticalValue<__VA_ARGS__>::value >::type
其中IdenticalType和IdenticalValue是为了避免在宏展开的时候的一些问题。在大多数情况下,两个Identical是不需要的。
另一个实现是:
template
struct IdenticalIfTrue {
};
template
struct IdenticalIfTrue<1, T> {
template
struct IdenticalType {
typedef U type;
};
};
template
struct CheckCondition {
template
struct IdenticalIfTrueWrapper {
typedef typename IdenticalIfTrue::template IdenticalType::type type;
};
};
#define RETURN(...) typename CheckCondition<__VA_ARGS__>::
#define WHEN(...) IdenticalIfTrueWrapper<__VA_ARGS__>::type
该实现将T和ok分两次传入,目的是避免前一个实现所说的宏展开的问题。但是,对于CheckCondition的匹配总是成功的,然后内层的IdenticalIfTrueWrapper也是成功的,最后到IdenticalIfTrue才失败。这样的问题是:编译器首先给出了上述匹配失败的错误信息,再给出a * b的失败。而之前的错误信息是首先指出a * b失败,再列出所有的候选函数模板。当然,哪种错误更友好没有定论。
4.不足
对于背景和实现中的错误信息,可以看出,由于匹配失败,列出了所有candidate导致过多的错误信息。