【问题标题】:Template argument deduction inconsistent模板参数推导不一致
【发布时间】:2020-06-17 10:51:34
【问题描述】:

以下代码在 MSVC 2019 和 Clang 主干上编译和运行。 (我认为它至少需要 C++17)。它不能在 gcc-trunk 上运行,我认为这是由于 gcc 中的错误造成的。

但是,当任何元素被用户类型或指针类型替换时,它在所有编译器上都会失败。要查看此内容,请取消注释末尾附近的 tuple_c 定义。

我实际上有点惊讶它的工作原理,因为它似乎是用一个具有通用引用参数的函数和一个具有 r-value-ref 参数的函数。也许没关系?如果是,为什么结构会失败?

有没有更好的写法?我的意思是一般。我很清楚std::tuple

#include <iostream>
using namespace std;
template <typename... TP> class Tuple
{
};
template <> class Tuple <>
{
};
template <typename Head, typename... Tail> class Tuple <Head, Tail...>
{
  Head head;

  Tuple <Tail...> tail;
public:
  Tuple ()
  {
  }

  Tuple (const Head& head_in, const Tail&...tail_in)
    : head (head_in), tail (tail_in...)
  {
  }
  template <int i> auto Get ()
  {
    return tail.template Get <i-1> ();
  }

  template <> auto Get <0> ()
  {
    return head;
  }

  template <int i, typename T> void Set (T&& v) // T&& is a universal ref
  {
    tail.template Set <i-1, T> (static_cast <T&&> (v));
  }
  template <int i, typename T> void Set (const T& v)
  {
    tail.template Set <i-1, T> (v);
  }
  template <> void Set <0, Head> (Head&& v) // Head&& is an rv-ref
  {
    head = v;
  }
  template <> void Set <0, Head> (const Head& v)
  {
    head = v;
  }
};
template <typename Head, typename... Tail> Tuple <Head, Tail...> MakeTuple (Head&& head, Tail&&...tail)
{
  Tuple <Head, Tail...> result (head, tail...);

  return result;
}
struct S
{
  int x;
  int y;
};
ostream& operator << (ostream& out, const S& s)
{
  out << "{" << s.x << "," << s.y << "}";
  return out;
}
int main(int argc, char* argv[])
{

  auto tuple_a = MakeTuple (1,2,3,4);
  tuple_a.Set <1,int> (42);
  cout << tuple_a.Get <0> () << '\n';
  cout << tuple_a.Get <1> () << '\n';
  cout << tuple_a.Get <2> () << '\n';
  cout << tuple_a.Get <3> () << '\n';
  auto tuple_b = MakeTuple (1,2.3f,3,4);
  tuple_b.Set <1,float> (42.3f);
  cout << tuple_b.Get <0> () << '\n';
  cout << tuple_b.Get <1> () << '\n';
  cout << tuple_b.Get <2> () << '\n';
  cout << tuple_b.Get <3> () << '\n';

  S s {4,5};
  //auto tuple_c = MakeTuple (1,2.3f,3,s);
  return 0;
}

【问题讨论】:

  • 请创建一个minimal reproducible example。您的大部分代码与您的问题无关,只是增加了我们必须扫除的噪音。
  • 还添加了确切的编译器错误消息,并指出它发生在哪一行。
  • gcc 10 在到达main() 部分之前就放弃了。如果没有明确的错误消息和minimal reproducible example,则无法重现所提出的问题。
  • 我确实尝试通过手动扩展实例化来缩小范围。在我看来,它看起来并不简单。我假设每个人都可以复制粘贴到编译器资源管理器中。我从编译器资源管理器中复制粘贴了它,所以它应该可以工作。 MSVC 说:source>(46): error C2535: 'void Tuple::Set(Head)': member function already defined or declared – Rob190 just now

标签: c++ templates variadic-templates template-argument-deduction


【解决方案1】:

首先,在CWG 727 之前,您不能在类范围内专门化成员函数模板。您将不得不使用constexpr-if、标签调度或SFINAE 来处理i==0 案例。

中使用std::enable_if_t 将是:

template <int i, typename T>
std::enable_if_t<i != 0> Set(T&& v)
{
    tail.template Set<i-1>(static_cast<T&&>(v));
}

template <int i, typename T>
std::enable_if_t<i == 0> Set(T&& v)
{
    head = static_cast<T&&>(v);
} 

中使用constexpr-if 变成:

template <int i, typename T>
void Set(T&& v)
{
    if constexpr (i == 0) head = static_cast<T&&>(v);
    else tail.template Set<i-1>(static_cast<T&&>(v));
}

其次,一旦编译器允许你在一个类中专门化一个函数模板,你当前的方法就会出现另一个问题。您的 MakeTuple 实现,由于模板参数推导如何用于转发引用,创建了一个引用类型元组,该元组对应于作为左值的 MakeTuple 参数:

template <typename Head, typename... Tail>
Tuple<Head, Tail...> MakeTuple(Head&& head, Tail&&... tail);

这使您的评论/假设:

void Set<0, Head>(Head&& v) // Head&& is an rv-ref

无效。

即对于左值表达式s

S s{ 4, 5 };
MakeTuple(s);

推导出的HeadS&amp;(也是引用折叠后head的类型)。然后编译器尝试实例化Tuple&lt;S&amp;&gt; 并生成以下两个声明:

void Set<0, S&>(S& && v); 

void Set<0, S&>(S& const& v);

在引用折叠后,它最终是:

void Set<0, S&>(S& v);

void Set<0, S&>(S& v);

此时,不仅两个定义相同,而且编译器也无法决定哪个主函数模板:

template <int i, typename T>
void Set(T&& v);

template <int i, typename T>
void Set(const T& v);

它们是专业化的,因为使用T=S&amp; 匹配两者。这可以通过在将每种类型存储到元组之前衰减每种类型来解决:

template <typename Head, typename... Tail>
Tuple<std::decay_t<Head>, std::decay_t<Tail>...> MakeTuple(Head&& head, Tail&&... tail);

【讨论】:

  • 我的理解是现在允许专门化成员函数的能力。我相信 gcc 的错误是:gcc.gnu.org/bugzilla/show_bug.cgi?id=85282
  • 感谢您的回答。它解决了我的主要问题,我现在看到问题是向 MakeTuple 函数提供了左值还是右值。如果我将 's' 替换为 'S()',它就会编译。仍在处理后果。
【解决方案2】:

上面已经回答了我的问题,但我认为包含解决方案的完整代码可能会很有用。它现在适用于除数组和字符串文字之外的所有类型。

#include <iostream>
using namespace std;

template <typename... TP> class Tuple
{
};

template <> class Tuple <>
{
};

template <typename Head, typename... Tail> class Tuple <Head, Tail...>
{
  Head head;
  Tuple <Tail...> tail;

public:
  Tuple ()
  {
  }

  Tuple (const Head& head_in, const Tail&...tail_in)
    : head (head_in), tail (tail_in...)
  {
  }

  template <int i> auto Get ()
  {
    return tail.template Get <i-1> ();
  }

  template <> auto Get <0> ()
  {
    return head;
  }

  template <int i, typename T> void Set (T&& v) 
  {
    tail.template Set <i-1, T> (static_cast <T&&> (v));
  }

  template <int i, typename T> void Set (const T& v)
  {
    tail.template Set <i-1, typename std::decay<T>::type> (v);
  }

  template <> void Set <0, typename std::decay<Head>::type> (Head&& v) 
  {
    head = v;
  }

  template <> void Set <0, typename std::decay<Head>::type> (const Head& v)
  {
    head = v;
  }
};

template <typename Head, typename...Tail> Tuple <typename std::decay <Head>::type, typename std::decay<Tail>::type...> MakeTuple (Head&& head, Tail&&...tail)
{
  Tuple <typename std::decay <Head>::type, typename std::decay<Tail>::type...> result (head, tail...);
  return result;  
}

struct S
{
  int x;
  int y;
};

ostream& operator << (ostream& out, const S& s)
{
  out << "{" << s.x << "," << s.y << "}";
  return out;
}

int main(int argc, char* argv[])
{
  const char* p = "hello";
  S s;
  int v = 32;

  auto tuple_a = MakeTuple (1.0,v,p,s);

  cout << tuple_a.Get <0> () << endl;
  cout << tuple_a.Get <1> () << endl;
  cout << tuple_a.Get <2> () << endl;
  cout << tuple_a.Get <3> () << endl;

  S s_update {10,12};

  tuple_a.Set <3> (s_update);

  const char* p_update = "goodbye";

  tuple_a.Set <2> (p_update);

  cout << tuple_a.Get <0> () << endl;
  cout << tuple_a.Get <1> () << endl;
  cout << tuple_a.Get <2> () << endl;
  cout << tuple_a.Get <3> () << endl;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-03
    • 1970-01-01
    相关资源
    最近更新 更多