【问题标题】:C++11 Check two sets of variadic template arguments matchC++11 检查两组可变参数模板参数是否匹配
【发布时间】:2016-05-26 08:03:58
【问题描述】:

这个问题与我之前提出的关于在 C++11 中实现类似于 Qt 的信号/插槽的问题有关。

考虑以下(非常简化的信号调度器,在此示例中没有任何用处,只是为了演示模式/问题):

template< typename... TYPES >
class Signal
{
public:
    Signal()  = default;
    ~Signal() = default;

    template< typename... PARAMETERS >
    void broadcast( PARAMETERS &&... p )
    {
        // static_assert to confirm PARAMETERS can map to TYPES
    }
};

这很好用,但在实践中会发生一些不需要的类型转换。例如;

// acceptable use.
Signal< int, unsigned, float, char >().broadcast(   1, 2u, 0.f, 'a' );

// should fail compilation, first parameter is a float, 4th is an int.
Signal< int, unsigned, float, char >().broadcast( 0.f,  0, 0.f,   0 );

// acceptable use, first parameter is const, but it's convertible.
const int   i  = 3;
Signal< int, unsigned, float, char >().broadcast(  i, 2u, 0.f, 'a');

// acceptable use, first parameter is const &, but it's convertible.
const int & j = i;
Signal< int, unsigned, float, char >().broadcast(  j, 2u, 0.f, 'a');

不应该有静默的 float 到 int 的转换。在这种情况下应该可以转换 const/const & (TYPES 的格式不应该有 const 或 & 因为所有数据都应该按值传递)。

我想阻止发生这种不需要的类型转换的编译。我想将 TYPES 和 PARAMETERS 都包装在元组中,遍历元组并确认给定元组参数索引中的每种类型都匹配(包括使用std::decay),但后来我在编译时看不到这样做的方法时间以便它可以进入static_assert

作为参考,选择的编译器是 clang(OS X 7.3 上的最新版本 (clang-703.0.31))和 vc14。

我想做的事情是否可行,如果可以,任何人都可以提供任何指示吗?

【问题讨论】:

    标签: c++11 variadic-templates type-safety


    【解决方案1】:

    这是我很快想到的一个元程序。它有点粗糙,但可以以更好的方式实现。您可能应该在元程序中使用衰减类型(std::decay)来获得正确的结果。

    #include <iostream>
    #include <type_traits>
    
    template <typename... T> struct param_pack {};
    
    template <typename, typename> struct is_all_same_impl;
    
    template <>
    struct is_all_same_impl<param_pack<>, param_pack<>>
    {
      static bool const value = true;
    };
    
    template <typename T, typename S, typename... Rest, typename... SRest>
    struct is_all_same_impl<param_pack<T, Rest...>, param_pack<S, SRest...>>
    {
      static bool const value = false;
    };
    
    template <typename T, typename... Rest, typename... SRest>
    struct is_all_same_impl<param_pack<T, Rest...>, param_pack<T, SRest...>>
    {
      static bool const value = is_all_same_impl<param_pack<Rest...>, param_pack<SRest...>>::value;
    };
    
    template <typename, typename>
    struct is_all_same;
    
    template <typename... FSet, typename... SSet>
    struct is_all_same<param_pack<FSet...>, param_pack<SSet...>>: is_all_same_impl<param_pack<FSet...>, param_pack<SSet...>> {};
    
    int main() {
      std::cout << is_all_same<param_pack<int, char, float>, param_pack<int, char, int>>::value << std::endl;
      return 0;
    }
    

    更新 :: 更简单的版本

    template <typename... T> struct param_pack {};
    
    int main() {
      std::cout << std::is_same<param_pack<int, float, int>, param_pack<int,float,int>>::value << std::endl;
      return 0;
    }
    

    所以你可以这样做:

    static_assert( is_same<param_pack<Args...>, param_pack<std::decay_t<Dargs>...>>::value, "Parameters do not sufficiently match." );
    

    【讨论】:

    • 似乎至少在 vc14 下的粗略测试中可以正常工作,尽管我似乎无法将 std::decay 放入元程序中,尽管将它放在 static_assert 似乎 工作; static_assert( is_all_same&lt;param_pack&lt;Args...&gt;, param_pack&lt;std::decay_t&lt;Dargs&gt;...&gt;&gt;::value, "Parameters do not sufficiently match." );
    • @Slartibartfast 是的,这就是我的意思。在调用 is_all_same 时放入 std::decay。抱歉不清楚,回复时有点着急:)
    • 您介意详细说明它是如何/为什么起作用的吗?我可以看到 value 在 3 个可能的位置定义,但我很困惑如何选择每个位置,特别是因为一些 is_all_same_impl 实现采用了我所期望的更多模板参数。
    • @Slartibartfast 我的第一个解决方案肯定是过度设计的。检查我更新的答案..更简单:)
    【解决方案2】:

    使用(再次)来自 Columbo 的 all_true bool pack trick

    template <bool...> struct bool_pack;
    template <bool... v>
    using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>;
    
    template <class... Args>
    struct Signal {
        template <class... Dargs, class = typename std::enable_if<all_true<
            std::is_same<Args, typename std::decay<Dargs>::type>{}...
        >{}>::type>
        void broadcast(Dargs &&...) {}
    };
    

    如果参数不完全匹配,此 SFINAE 将退出该函数。

    【讨论】:

    • 嗯,似乎在 vc14 中不起作用。因此使用Signal&lt; int, float, char &gt; Test; Test.broadcast( 1, 1.f, 'a' ); vc14 抱怨; 1&gt;C:\Development\tinkering\source\main.cpp(28): error C2783: 'void Signal&lt;int,float,char&gt;::broadcast(Dargs &amp;&amp;...)': could not deduce template argument for '&lt;unnamed-symbol&gt;' 1&gt; C:\Development\tinkering\source\main.cpp(20): note: see declaration of 'Signal&lt;int,float,char&gt;::broadcast' - 直到明天才能尝试 clang。
    • @Slartibartfast 我猜这是 MSVC 损坏的 SFINAE 的另一个实例...作为一种解决方法,您可以在函数内使用static_assert(all_true&lt;...&gt;, "");
    • 也许我误解了解决方法,但我仍然无法让 vc14 吞下它。它确实可以在 clang 上按要求工作。遗憾的是,这看起来比@Arunmu 建议的解决方案更好,但是另一种解决方案似乎可以在我目前使用的两个编译器上运行而无需大惊小怪,所以在这种情况下,我倾向于另一个答案。
    • @Slartibartfast 我确实不清楚。我建议您从模板参数中删除, class = std::enable_if&lt;...&gt;,而不是static_assert 它在函数中的内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    • 1970-01-01
    • 2023-04-02
    • 1970-01-01
    • 2015-11-10
    相关资源
    最近更新 更多