7

用什么替代 C++ 中的概念(即将推出的功能)?

您可能听说过 C++ 中的概念。该功能允许您指定对模板中的类型的要求。

我现在正在寻找一种方法来做到这一点,我发现的最好的方法是在 Stroustrup 的书中,他将谓词与 static_assert 一起使用,如下所示:

template<typename Iter, typename Val>
Iter find(Iter b, Iter e, Val x) 
{
    static_assert(Input_iterator<Iter>(),"find(): Iter is not a Forward iterator");

    // Rest of code...
}

如果您使用其他方法或者这个方法有问题,请告诉我。

4

3 回答 3

1

好吧,有几次我需要类似概念的功能,我转向了Boost Concept Check。它不是最漂亮的库,但它似乎已经内置了很多东西。

使用您的方法我遇到的唯一问题是需要编写所有特征类。我没有广泛使用它,但大概很多常见的东西已经用 Boost 为你完成了。

于 2014-02-12T16:28:36.423 回答
1

有一种 C++03 方法可以执行概念提供的部分编译时检查。

检查是否提供了特定成员

概念可以定义如下(if(0)用于抑制链接期间的错误。(void)test#用于抑制未使用的变量警告。):

template <class T>
struct ForwardIterator {
  ForwardIterator() {
    if(0) {
      void (T::* test1) () = &T::operator++; (void)test1;
    }
  }
};

template <class T>
struct BidirectionalIterator {
  BidirectionalIterator() {
    if(0) {
      ForwardIterator<T> requirement_1;
      void (T::* test1) () = &T::operator--; (void)test1;
    }
  }
};

并且可以在编译时使用模板实例化进行测试:

struct FooIterator {
  void operator++() {}
};

template struct BidirectionalIterator<FooIterator>;

它的额外好处是提供编译错误(一旦你习惯了它们)比 C++11 的 static_assert 提供的那些更易读。例如,gcc 给出以下错误:

concept_test.cpp: In instantiation of ‘BidirectionalIterator<T>::BidirectionalIterator() [with T = FooIterator]’:                                                                                                          
concept_test.cpp:24:17:   required from here                                                                                                                                                                               
concept_test.cpp:15:30: error: ‘operator--’ is not a member of ‘FooIterator’                                                                                                                                               
       void (T::* test1) () = &T::operator--; (void)test1;                                                                                                                                                                 
                              ^                                                                                                                                                                                            

甚至 MSVC2010 也会生成一个有用的编译错误,其中包括导致错误的模板参数 T 的值。它不会对 static_asserts 执行此操作。

检查参数类型和返回值

如果参数/返回类型取决于被测试的类,则被测试的类必须提供必要的 typedef。例如,以下概念测试一个类是否提供返回前向迭代器的开始和结束函数:

template <class T>
struct ForwardIterable {
  ForwardIterable() {
    if(0) {
      ForwardIterator<typename T::Iterator> requirement_1;
      typename T::Iterator (T::* test1) () = &T::begin; (void)test1;
      typename T::Iterator (T::* test2) () = &T::end;   (void)test2;
    }
  }
};

并且使用如下(注意typedef是必须的):

struct SomeCollection {
  typedef FooIterator Iterator;
  Iterator begin();
  Iterator end();
};

template struct ForwardIterable<SomeCollection>;

签名检查

此方法还广泛检查签名。在下面的代码中,编译器会检测到参数 ofmodifyFooItem不应该是 const。

struct SomeFoo;

template <class T>
struct TestBar {
  TestBar() {
    if(0) {
      int (T::* test1) (SomeFoo * item) = &T::modifyFooItem; (void)test1;
    }
  }
};

struct SomeBar {
  int modifyFooItem(const SomeFoo * item) {}
};

template struct TestBar<SomeBar>;

它会产生以下错误:

concept_test.cpp: In instantiation of ‘TestBar<T>::TestBar() [with T = SomeBar]’:
concept_test.cpp:61:17:   required from here
concept_test.cpp:52:47: error: cannot convert ‘int (SomeBar::*)(const SomeFoo*)’ to ‘int (SomeBar::*)(SomeFoo*)’ in initialization
       int (T::* test1) (SomeFoo * item) = &T::modifyFooItem; (void)test1;
于 2014-03-08T13:33:52.573 回答
1

检查概念的最佳方法是使用替换失败。但是,在 C++98 中,使用替换失败的检测是相当有限的。在 C++11 中,我们可以使用更强大的表达式替换失败。C++11 中的Tick库提供了定义概念谓词的简单方法。例如,一个 quick and dirtyis_input_iterator可以这样写:

TICK_TRAIT(is_input_iterator,
    std::is_copy_constructible<_>)
{
    template<class I>
    auto requires_(I&& i) -> TICK_VALID(
        *i,
        ++i,
        i++,
        *i++
    );
};

然后Tick还提供了一个TICK_REQUIRES宏来添加模板约束(它只处理所有的enable_if样板文件),所以你可以像这样定义函数:

template<typename Iter, typename Val, TICK_REQUIRES(is_input_iterator<Iter>())>
Iter find(Iter b, Iter e, Val x) 
{
    // Rest of code...
}

理想情况下,您不想使用它,static_assert因为它会产生编译错误。所以我们无法检测 say 是否find在使用某些参数调用时有效,因为它会在无效时产生编译器错误。

于 2014-07-01T22:16:00.437 回答