模板参数的编译时比较

5

我有一个需求,如果模板参数中传递的整数大于某个特定值,我应该使用特定的类。否则,我应该在编译时出现错误...

就像以下代码:

enum Time { Day, Week, Month };

template<Time t, int length>
class Timer
{
}

现在,我必须限制实例化Timer的方式,以便 - Timer<Day,8>Timer<Day,9>等应该能够工作,但是使用Daylength不能小于8。
同样,当与Week一起使用时,length不能小于10,等等...
有没有人可以告诉我如何在编译时实现这个目标?

可能是https://dev59.com/zVDTa4cB1Zd3GeqPNvvL的重复问题。 - Grigor Gevorgyan
5个回答

7

其他答案都采用元编程来检测条件,而我则希望保持简单:

template<Time t, int length>
class Timer
{
    static_assert( (t == Day && length > 7) 
                 ||(t == Week && length > 10)
                 ||(t == Month && length > 99), "Invalid parameters"
};

如果条件不满足,编译器将触发断言,并且通过错误消息和/或查看行很容易进行验证。

使用SFINAE工具禁用类型的版本也可以实现相同的结果:代码将无法编译,但代价是使错误消息更加复杂难懂:什么意思,Timer<Day,5> 不是一种类型吗?当然它是,它是 Timer<Time,int> 的实例化!

编辑:上述的 static_assert 是在C++0x中实现的,在没有C++0x的编译器中,您可以将其实现为宏:

#define static_assert( cond, name ) typedef char sassert_##name[ (cond)? 1 : -1 ];

这个简单的宏不接受一个字符串字面值作为第二个参数,而是一个单词。使用方法如下:

static_assert( sizeof(int)==4, InvalidIntegerSize ) )

错误消息需要进行一些人工解析,因为编译器会抱怨(如果条件不满足),sassert_InvalidIntegerSize的大小是负数。


你应该提到这是C++0x的解决方案。楼主可能不知道。 - Nawaz
@Nawaz:static_assert有不同的版本,其中一些是C++0x,而另一些则不是。最简单的static_assert实现是#define static_assert( cond, name ) typedef char sassert_##name[ (cond)? 1 : -1];,但是它当然不会接受字符串字面量作为第二个参数,因此它将被用作:static_assert( ..., InvalidParameters ),错误消息会稍微糟糕一些,类似于size of array 'sassert_InvalidParameters' is negative。我们目前在我们的代码库中使用类似于这样的宏,并且编译器不支持C++0x。 - David Rodríguez - dribeas
如果你选择这种方式,那么错误信息并不像你原帖所说的那么好。现在,在“有用的错误信息”方面,模板元编程和这个解决方案一样出色。 - Nawaz
@Nawaz:错误信息将包含一个已知的字符字符串sassert_,后面跟着一个描述性的意义InvalidParameters(虽然无效参数不是那么描述性,但是InvalidTimerPeriod可能更好)。另一方面,使用TMP方法只会得到一个invalid use of undefined type X(在您的测试中读取错误消息)。您是否使用了前向声明并忘记包含适当的类型?还是条件不正确?为什么会有关于rule<false>的投诉?我在任何地方都没有使用它!静态断言将指向检查失败的确切行。 - David Rodríguez - dribeas
整个概念提案以及有关C++编译器抛出的错误消息的许多抱怨都属于同一问题:错误消息指向实现中你没有在代码中使用到的一些细节,并且甚至不告诉你底层问题是什么。概念的想法是,它们会像static_assert或者类型所需特性那样产生更好的错误报告,而不是深入模板化代码中的错误,这些错误对人类来说很难解释。 - David Rodríguez - dribeas

6
length >= 8的结果作为bool模板参数传递给辅助模板。仅提供true的专门化。话虽如此,这听起来像是一项作业,所以我会把编码留给你。
祝好!

3
是的,真正的解决方案是使用 static_assert,你所描述的是实现 static_assert 的一种方式。 - sharptooth

3
你可以这样做:
template<bool> 
struct rule;

template<> 
struct rule<true> {};

template<Time Tm, int Len>
struct constraint;

//Rule for Day     
template<int Len>
struct constraint<Day, Len> : rule<(Len>= 8)>
{};

template<Time Tm, int Len>
class Timer : constraint<Tm, Len>
{
   //your code
};

测试代码:

int main() {
        Timer<Day, 7> timer1; //error 
        Timer<Day, 8> timer2; //okay
        return 0;
}

在线演示:


同样,您可以添加WeekMonth的规则,如下:

//Rule for Week
template<int Len>
struct constraint<Week, Len> : rule<(Len>= 10)>
{};

//Rule for Month
template<int Len>
struct constraint<Month, Len> : rule<(Len>= 100)>
{};

2

这种验证的想法通常是将工作交给专门的帮助类,您可以为每种类型的参数专门定制一个帮助类。

template <typename T, size_t V>
class Foo
{
  static_assert(helper<T,V>::value, "Wrong Parameters");
};

有两种方法可以进行验证:

// Require full specialization
template <typename T, size_t V> struct helper: std::false_type {};

template <>
struct helper<Bar,0>: std::true_type {};

template <>
struct helper<Bar,4>: std::true_type {};


// Property like
template <typename T, size_t V> struct helper: std::false_type {};

template <size_t V>
struct helper<Bar, V>: std::integral_constant<bool, V <= 8> {};

当然,这些假设使用了C++0x的功能。在C++03中,你需要自己提供简单的true_typefalse_typeintegral_constant类。

0
enum Time { Day, Week, Month };
template<Time T> struct Length;
template<> struct Length<Day> { static const int value = 8 ; };
template<> struct Length<Week> { static const int value = 9; };
template<> struct Length<Month> { static const int value = 10; };

template<bool b> struct Limit;
template<> struct Limit<true> { typedef bool yes; };

#define COMPILE_ASSERT(V) typedef typename Limit<(V)>::yes checked

template<Time t, int length>
class Timer
{
  COMPILE_ASSERT(length >= Length<t>::value);
};

请查看此处的演示


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接