【问题标题】:Metaprogramming and SFINAE/std::enable_if: infinite template recursion元编程和 SFINAE/std::enable_if:无限模板递归
【发布时间】:2013-12-12 23:02:27
【问题描述】:

考虑以下程序:

// Include
#include <iostream>
#include <type_traits>
#include <utility>
#include <tuple>
#include <string>

// Base class
template <class Crtp, class... Types>
struct Base
{
    // Constructor calling the transmute function
    template <class... OtherTypes> 
    explicit inline Base(const OtherTypes&... source) 
    : _data(transmute<std::tuple<Types...>>(std::forward_as_tuple(source...))) 
    {;}

    // Transmute: create a new object
    template <class Output> 
    static constexpr Output transmute() 
    {return Output();}

    // Transmute: forward existing object
    template <class Output, 
              class Input, 
              class = typename std::enable_if<
                          std::is_convertible<
                              typename std::remove_cv<typename std::remove_reference<Input>::type>::type, 
                              typename std::remove_cv<typename std::remove_reference<Output>::type>::type
                          >::value
                      >::type> 
    static constexpr Input transmute(Input&& input) 
    {return std::forward<Input>(input);} 

    // Transmute: recursive transmutation
    template <class Output, 
              class... Input, 
              class = typename std::enable_if<
                          (sizeof...(Input) <= std::tuple_size<Output>::value) 
                          && (sizeof...(Input) != std::tuple_size<Output>::value)
                      >::type>
    static constexpr Output transmute(const Input&... input) 
    {return transmute<Output>(input..., typename std::tuple_element<sizeof...(Input), Output>::type());}

    // Transmute: final step
    template <class Output, 
              class... Input, 
              class = typename std::enable_if<
                          (sizeof...(Input) == std::tuple_size<Output>::value) 
                          && (sizeof...(Input) != 0)
                      >::type> 
    static constexpr Output transmute(Input&&... input) 
    {return transmute<Output>(std::forward_as_tuple(std::forward<Input>(input)...));}

    // Data member
    std::tuple<Types...> _data; 
};

// Derived class
struct Derived
: public Base<Derived, std::string, bool>
{
    // Universal reference constructor
    template <class... Misc> 
    explicit inline Derived(Misc&&... misc) 
    : Base<Derived, std::string, bool>(std::forward<Misc>(misc)...) 
    {;}
};

// Main
int main(int argc, char* argv[])
{
    Derived a("hello"); // Boom !!!!
    return 0;
}

如果你试图编译它,编译器会“爆炸”,抛出一个相当令人印象深刻的错误,模板的模板模板......

我的问题很简单:问题出在哪里,如何解决?

【问题讨论】:

  • 因此类似于摆出整个程序并输入,然后说它“运行时崩溃”。出了什么问题?做更少的事情。测试每个组件。找到从测试和工作到测试和失败的变化。包括有关您正在尝试做的事情的大量文档。尝试用更少的麻烦来简化并产生相同的错误。
  • (sizeof...(Input) &lt;= std::tuple_size&lt;Output&gt;::value) &amp;&amp; (sizeof...(Input) != std::tuple_size&lt;Output&gt;::value) 这看起来很可疑。
  • : _data(transmute&lt;std::tuple&lt;Types...&gt;&gt;(source...)) 并编译(删除了std::forward_as_tuple)。不过,不确定它是否正确。但据我了解您的元编程,作为元组的转发是不正确的:最后一步期望所有参数 not 都包装在一个元组中;在您当前的代码中,从"hello" 创建的std::tuple&lt;const char(&amp;)[6]&gt; 不能转换为std::string,因此永远不会发生转发。
  • +1,我刚刚发现这是 3 分钟前错误的根源。
  • 只接受 mpark 的。写重复的答案没有任何好处。

标签: c++ c++11 metaprogramming template-meta-programming sfinae


【解决方案1】:

如果我正确理解您的意图,您似乎想要将M 参数传递给大小为Nstd::tuple&lt;&gt;,其中M &lt;= N。如果是M &lt; N,则填写该类型的默认构造值未提供的参数。

如果是这样,Base 的构造函数应该是:

template <class... OtherTypes>
explicit inline Base(const OtherTypes&... source)
: _data(transmute<std::tuple<Types...>>(source...))
{;}

这样它将通过递归,最终,然后是forward_as_tuple

替代解决方案:

注意:std::integer_sequence 和相关帮助程序的实现被省略。你可以参考论文N3658和一个实现here

template <typename T, typename Seq, T Begin>
struct make_integer_range_impl;

template <typename T, T... Ints, T Begin>
struct make_integer_range_impl<T, std::integer_sequence<T, Ints...>, Begin>
    : public std::common_type<std::integer_sequence<T, Begin + Ints...>> {};

/* Similar to std::make_integer_sequence<>, except it goes from [Begin, End)
   instead of [0, Size). */
template <typename T, T Begin, T End>
using make_integer_range = typename make_integer_range_impl<
    T, std::make_integer_sequence<T, End - Begin>, Begin>::type;

/* Similar to std::make_index_sequence<>, except it goes from [Begin, End)
   instead of [0, Size). */
template <std::size_t Begin, std::size_t End>
using make_index_range = make_integer_range<std::size_t, Begin, End>;

/* Trivial wrapper for std::tuple_element<>. */
template <std::size_t Idx, typename Tuple>
using tuple_element_t = typename std::tuple_element<Idx, Tuple>::type;

template <typename... Args>
class Partial {
  public:

  /* Our tuple type. */
  using Tuple = std::tuple<Args...>;

  /* Create an index_range, [# of arguments, tuple size),
     and forward the arguments to the delegating constructor. */
  template <typename... ForwardArgs>
  Partial(ForwardArgs &&... forward_args)
      : Partial(make_index_range<sizeof...(ForwardArgs),
                                 std::tuple_size<Tuple>::value>(),
                std::forward<ForwardArgs>(forward_args)...) {}

  private:

  /* The given indices are for the missing arguments, get the corresponding
     types out of the tuple and default construct them. */
  template <std::size_t... Indices, typename... ForwardArgs>
  Partial(std::index_sequence<Indices...> &&, ForwardArgs &&... forward_args)
      : tuple_{std::forward<ForwardArgs>(forward_args)...,
               tuple_element_t<Indices, Tuple>{}...} {}

  /* Our tuple instance. */
  Tuple tuple_;

};  // Partial<Args...>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-18
    相关资源
    最近更新 更多