【问题标题】:Is there a case where vararg functions should be preferred over variadic templates?是否存在应首选可变参数函数而不是可变参数模板的情况?
【发布时间】:2017-03-13 08:39:34
【问题描述】:

可变参数模板有很多优点,但是在某些情况下应该使用 C 风格的可变参数函数(使用<cstdarg>)来代替吗?

【问题讨论】:

    标签: c++ c++11 variadic-templates variadic-functions


    【解决方案1】:
    1. 如果您提供具有 C++ 实现的 C API,则模板不是该 API 的选项。可变参数是。

    2. 如果您需要支持不支持 C++11 或更新标准的编译器,则不提供可变参数模板。可变参数是。

    3. 如果您需要编译防火墙。 IE。您需要从标题中隐藏函数的实现,那么可变参数模板不是一个选项。可变参数是。

    4. 在内存受限的系统(嵌入式)上,模板生成的不同功能可能会导致过多的膨胀。也就是说,此类系统通常也是实时的,在这种情况下,由于分支和堆栈的使用,可变参数也可能不可接受。

    【讨论】:

    • 可变参数也用于一些元编程(例如用 SFINAE 实现的特征),因为它们的属性在重载决议中被认为是最后一个。
    • @bolov 酷!我从未遇到过这种用例。听起来有点聪明,但对我有好处:)
    • @bolov 你介意提供更多细节,甚至是代码示例或链接
    • @user2079303 如果您有兴趣,我已经添加了一个示例。
    • @bolov 这种用法有一个可能的警告:编译器可能更难优化可变参数函数。至少 MSVC2015 难以优化此类代码 - 它没有内联琐碎的函数,没有通过调用传播已知值等。使用不同的重载解决规则可能对性能有益。我使用了指针与布尔(当使用指针调用时,布尔版本的排名低于指针版本),编译器能够将复杂的调用链折叠成单个优化函数。
    【解决方案2】:

    我想添加到excellent answer of @user2079303

    可变参数也用于某些元编程(例如使用 SFINAE 实现的特征),因为它们在重载决议中被认为是最后一个属性。

    假设我们想要实现一个 trait 来检测一个类是否可以从某些类型构造(比如 std::is_constructible:

    简化的现代实现将是这样的(这不是唯一的方法:正如所指出的,void_t 也可以 be used to implement the trait 并且很快(2020 年?)我们将不再需要这些噱头,因为 concepts 是以一等公民的身份使用require 子句):

    template <class T, class... Args> struct Is_constructible {  
    
      template <class... Params>
      static auto test(Params... params) -> decltype(T{params...}, std::true_type{});
    
      static auto test(...) -> std::false_type;
    
      static constexpr bool value = decltype(test(std::declval<Args>()...))::value;
    };
    

    这是因为SFINAE 起作用:当实例化test 时,如果语义由于某些dependent name 而无效,那么这不是硬错误,而是忽略了重载。

    如果您想了解更多关于 trait 技巧及其实现方式和工作原理的信息,您可以进一步阅读:sfinae idiommember detector idiomenable_if idiom

    所以,X 类型只能由 2 个整数构造:

    struct X { X(int, int) {}; };
    

    我们得到这些结果:

    Is_constructible<X, int, int>::value // true
    Is_constructible<X, int>::value;     // false
    

    现在的问题是我们是否可以用可变参数模板替换可变参数测试:

    template <class T, class... Args> struct Is_constructible_broken {  
    
      template <class... Params>
      static auto test(Params... params) -> decltype(T{params...}, std::true_type{});
    
      template <class... Params>
      static auto test(Params...) -> std::false_type;
    
      static constexpr bool value = decltype(test(std::declval<Args>()...))::value;
    };
    

    答案是否定的(至少不是直接替代)。当我们实例化

    Is_constructible_broken<X, int, int>::value
    

    我们得到一个错误:

    错误:重载 'test(int, int)' 的调用不明确

    因为这两个重载都是可行的,并且在重载决议中都具有相同的“等级”。第一个使用可变参数的实现是可行的,因为即使两个重载都是可行的,可变参数模板还是首选可变参数模板。

    事实证明,您实际上可以使其与可变参数模板一起使用。诀窍是向test 添加一个人为参数,该参数完美匹配第一个重载并转换为第二个:

    struct overload_low_priority{};
    struct overload_high_priority : overload_low_priority {};
    
    template <class T, class... Args> struct Is_constructible2 {  
    
      template <class... Params>
      static auto test(overload_high_priority, Params... params)
          -> decltype(T{params...}, std::true_type{});
    
      template <class... Params>
      static auto test(overload_low_priority, Params...) -> std::false_type;
    
      static constexpr bool value
          = decltype(test(overload_high_priority{}, std::declval<Args>()...))::value;
    };
    

    但我认为在这种情况下可变参数更清楚。

    【讨论】:

    • 请注意,void_t 可能用于创建特征,而不是使用省略号。
    • @Jarod42 是的,很快require clause 也会过时
    • 我更喜欢 struct high_overload_priority : low_overload_priority {}; 使用而不是 int/long 来订购重载。
    • @Jarod42 是的,我想过,但我有点过火了 :)) godbolt.org/g/J81HkC
    • @bolov 很快就会过时
    【解决方案3】:

    vararg 允许使用__attribute__ format。例如

    void debug(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
    
    void f(float value)
    {
      debug("value = %d\n", value); // <- will show warning.
    }
    

    很遗憾,这不能使用可变参数模板来实现。

    编辑: 正如 Vladimir 指出的那样,我忘了提到 __attribute__ format 不是标准的一部分,但是 GCC 和 Clang(但不是 Visual Studio)都支持它。更多详情请见:https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes

    【讨论】:

    • 我建议你把 attribute 格式的使用链接,因为它不是标准的。我也认为展示它使用的一些编译器是件好事。我只知道 GCC。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-01
    相关资源
    最近更新 更多