简单的C++Concept实现:给模板参数加限制

本文介绍一种简单的C++ Concept实现:给模板参数加限制。


1.背景
#include <iostream>
using namespace std;
typedef long long int64;


struct BigInteger {
  BigInteger(int64 v): value(v) {}
  int64 value;
};


template<typename T>
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 <iostream>
#include <type_traits>
using namespace std;
typedef long long int64;


template<int ok>
struct CheckConditionThen{};


template<>
struct CheckConditionThen<true>{
  template<typename U>
  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<typename T>
REQUIRES(is_integral<T>::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<class T> typename CheckConditionThen<std::is_integral<_Tp>::value>::IdenticalType<BigInteger>::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<class T> typename CheckConditionThen<std::is_integral<_Tp>::value>::IdenticalType<BigInteger>::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<typename T>
struct IdenticalType {
  typedef T type;
};


template<int v>
struct IdenticalValue {
  enum {value = v};
};


template<typename T, int ok>
struct CheckCondition {
};


template<typename T>
struct CheckCondition<T, 1> {
  typedef T type;
};


#define RETURN(...) typename CheckCondition<typename IdenticalType<__VA_ARGS__>::type,
#define WHEN(...) IdenticalValue<__VA_ARGS__>::value >::type
其中IdenticalType和IdenticalValue是为了避免在宏展开的时候的一些问题。在大多数情况下,两个Identical是不需要的。


另一个实现是:
template<int ok, typename T>
struct IdenticalIfTrue {
};


template<typename T>
struct IdenticalIfTrue<1, T> {
  template<typename U>
  struct IdenticalType {
    typedef U type;
  };
};


template<typename T>
struct CheckCondition {
  template<int ok>
  struct IdenticalIfTrueWrapper {
    typedef typename IdenticalIfTrue<ok, T>::template IdenticalType<T>::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导致过多的错误信息。
展开阅读全文

没有更多推荐了,返回首页