【问题标题】:Testing member function, by peeling pairs off variadic macro/template/function?通过剥离可变参数宏/模板/函数对来测试成员函数?
【发布时间】:2017-10-25 10:49:03
【问题描述】:

(我正在使用catch 进行单元测试,不幸的是它还没有它所调用的生成器来做这种事情。)

c++17,有没有办法减少这种情况:

assert(""  == String{""  }.removeLeading(' '));
assert("a" == String{" a").removeLeading(' '));
assert("a" == String("a" }.removeLeading(' '));
assert("a" == String{"a "}.removeLeading(' '));

使用这样的宏、模板或函数:

#define MACRO(className, method, arg, ...) \
   for(auto [x, y] : { __VA_ARGS }) { \
      assert(x == className{y}.method(arg)); \
   }

所以它更短是这样的:

MACRO(String, removeLeading, ' ',
   { "", "" }, {"a", " a"}, {"a", "a"}, {"a", "a "})

// or

MACRO(String, removeLeading, ' ',
   "", "",    "a", " a",    "a", "a",    "a", "a ")

假设所有... 参数“auto”为同一类型。

基本上对... args 的数量没有限制。 (也许可以达到 100 个?)

使用第一个 MACRO() 给出:unable to deduce 'std::initializer_list<auto>&&' from...(它也不理解语义,所以严格按照 , 标记分开,但只要正确组合,没关系。)

使用第二个MACRO() 给出:cannot decompose non-array non-class type 'const char*'


尝试模板:

template<typename T>
void TEMP(T a, T b) {
    assert(a == String{ b }.removeLeading(' '));
}

template<typename T, typename... Args>
void TEMP(T a, T b, Args... args) {
    TEMP(a, b);
    TEMP(args...);
}

TEMP("", "",    "a", " ",    "a", "a",    "a", "a ");

这至少有效,但我不希望将 classNamemethodarg 硬编码为 "String""removeLeading"" "


我想知道是否有一种方法可以使用我没有做太多的所有新类型特征/“元”模板(不知道还能如何称呼它们)来解决这个问题。 (我查看了过去一两年内可用的一些库,它们对我来说几乎就像是一种不同的语言......)

【问题讨论】:

    标签: c++ templates variadic-templates c++17 variadic-macros


    【解决方案1】:

    这几乎可以工作:

    for (auto [x,y] : {{"a", "b"}, {"c", "d"}, {"e", "f"}}) {
        foo(x, y);
    }
    

    唯一的问题是内部的braced-init-lists 不能自己推导出来。所以我们只需要给编译器一点推动力。

    using P = std::pair<char const*, char const*>;
    for (auto [x,y] : {P{"a", "b"}, {"c", "d"}, {"e", "f"}}) {
        foo(x, y);
    }
    

    这行得通。只需识别第一个就足够了。或者,对于您的具体示例:

    for (auto [exp, arg] : {P{"", ""}, {"a", " a"}, {"a", "a"}, {"a", "a "}}) {
        assert(exp == String(arg).removeLeading(' '));
    }
    

    如果你真的想为此编写一个宏,那么现在应该很清楚如何做到这一点。

    【讨论】:

    • 在 clang 和 gcc 中效果很好。 VS2017 15.5 Preview 提供error C3312: no callable 'begin' function found for type 'initializer list''end'。我原来的帖子只指定了c++17,没有提到VS2017,当然VS2017还没有全部实现。这可能是 P0017R1。在 Visual Studio 赶上世界其他地方之前,您是否知道一种解决方法?
    • @user1902689 这个答案没有那个特性——这里唯一的 C++17 特性是结构化绑定。你可以试试std::initializer_list&lt;P&gt;{{"", ""}, {"a", " a"}, ...}?
    • 在 clang 和 gcc 上,std::pair&lt;any, any&gt; 甚至可以用于任何可以与 any 一起使用的东西,而不必指定参数类型。
    • 是的,VS 接受这个,pair&lt;char const*, char const*&gt;pair&lt;any, any&gt; 导致 VS2017 15.5 Preview 给出内部编译器错误。我会就此提交一份错误报告。我还将为原始 C3312 提交一份文件,因为结构化绑定被列为完全实现。
    【解决方案2】:

    我不希望将 className、method 和 arg 硬编码为 "String""removeLeading"" "

    如果我正确理解你想要什么,下面的结构 foo(带有静态方法 func())可以给你一个想法(但你必须微调细节)

    template <typename C, auto ... Vs>
    struct foo
     {
       template <typename R> 
       static void func (R(C::*)(decltype(Vs) const & ...))
        { }
    
       template <typename R, typename T1, typename T2, typename ... Args>
       static void func (R(C::*m)(decltype(Vs) const & ...),
                         T1 const & a, T2 const & b, Args const & ... args)
        {
          assert( a == (C{b}.*m)(Vs...) );
    
          func(m, args...);
        }
     };
    

    以下是一个完整的编译示例(运行失败,因为removeLeading() 没有真正实现;所以abort() 失败)

    #include <cassert>
    #include <string>
    
    // fake String strict, with fake removeLeading() method, to
    // demonstrative/compiling purposes 
    struct String
     {
       String (std::string const &) 
        { }
    
       std::string removeLeading (char const &)
        { return { }; }
     };
    
    template <typename C, auto ... Vs>
    struct foo
     {
       template <typename R> 
       static void func (R(C::*)(decltype(Vs) const & ...))
        { }
    
       template <typename R, typename T1, typename T2, typename ... Args>
       static void func (R(C::*m)(decltype(Vs) const & ...),
                         T1 const & a, T2 const & b, Args const & ... args)
        {
          assert( a == (C{b}.*m)(Vs...) );
    
          func(m, args...);
        }
     };
    
    int main()
     {
       foo<String, ' '>::func(&String::removeLeading,
          "", "",    "a", " ",    "a", "a",    "a", "a ");
     }
    

    -- 编辑--

    OP 编译器不支持模板非类型参数的 auto 类型。

    所以前面的解决方案(强烈基于auto)不能工作。

    我提出了另一种解决方案,兼容 C++14,应该可以工作。

    #include <tuple>
    #include <string>
    #include <cassert>
    #include <functional>
    
    // fake String strict, with fake removeLeading() method, to
    // demonstrative/compiling purposes 
    struct String
     {
       String (std::string const &) 
        { }
    
       std::string removeLeading (char const &)
        { return { }; }
     };
    
    template <typename C, typename ... Vs, std::size_t ... Is, typename R>
    void barH (std::tuple<Vs...> const &, std::index_sequence<Is...> const &,
               R(C::*)(Vs const & ...))
     { }
    
    template <typename C, typename ... Vs, std::size_t ... Is, typename R,
              typename T1, typename T2, typename ... Args>
    void barH (std::tuple<Vs...> const & tv,
               std::index_sequence<Is...> const & is, R(C::*m)(Vs const & ...),
               T1 const & a, T2 const & b, Args const & ... args)
     {
    
       assert( a == (C{b}.*m)(std::get<Is>(tv)...) );
    
       barH<C>(tv, is, m, args...);
     }
    
    template <typename C, typename ... Vs, typename R, typename ... Args>
    void bar (std::tuple<Vs...> const & tv, R(C::*m)(Vs const & ...),
              Args const & ... args)
     { barH<C>(tv, std::make_index_sequence<sizeof...(Vs)>{}, m, args...); }
    
    
    int main()
     {
       bar<String>(std::make_tuple(' '), &String::removeLeading,
                   "", "",    "a", " ",    "a", "a",    "a", "a ");
     }
    

    【讨论】:

    • 我喜欢这两种解决方案。另一个更简单,但我更喜欢这个,因为它不需要指定 in 和 out 参数类型。不知道为什么有人反对它。
    • 在 clang 和 gcc 中效果很好。 VS2017 15.5 预览版出现错误C3533: a parameter cannot have a type that contains 'auto'C2672: no matching overloaded function foundC2784: could not deduce template argumentC2780; expects 1 arguments - 9 provided。我原来的帖子只指定了c++17,没有提到VS2017,当然VS2017还没有全部实现。这可能是 PR0091R3 和 P0127R2。在 Visual Studio 赶上世界其他地方之前,您是否知道一种解决方法?
    • @user1902689 - 失去auto 用于模板值的类型,您将失去此解决方案的真正优势:两个可变参数列表,一个用于struct,一个用于方法。所以,我能想象的最好的方法是将一个列表包装在 std::tuple 中;不像 C++17 那样优雅,但(我认为)是可能的。给我几分钟...
    • @user1902689 - 一会儿:一个问题:你的 VS2017 15.5 支持 std::apply?
    • @user1902689 - 不幸的是,我不知道如何将std::apply 应用于方法。嗯...有一个基于std::getstd::index_sequence 的旧方法。答案改进了添加 C++14 解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-15
    • 1970-01-01
    相关资源
    最近更新 更多