Несогласованный вывод аргументов шаблона - PullRequest
2 голосов
/ 17 июня 2020

Следующий код компилируется и запускается в MSV C 2019 и стволе Clang. (Думаю, нужен как минимум C ++ 17). Он не работает на g cc -trunk, и я считаю, что консенсус состоит в том, что это связано с ошибкой в ​​g cc.

Однако, когда любой из элементов заменяется типом пользователя или тип указателя, он не работает на всех компиляторах. Чтобы увидеть это, раскомментируйте определение 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;
}

Ответы [ 2 ]

1 голос
/ 17 июня 2020

Во-первых, до CWG 727 вам не разрешается специализировать шаблон функции-члена внутри области класса. Вам нужно будет использовать constexpr-if, tag-dispatching или 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 аргументам, которые являются lvalue:

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

Это делает ваш комментарий / предположение:

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

недействительно.

То есть для выражения lvalue s:

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

выведенное Head равно S& (это также тип head после свертывания ссылки). Затем компилятор пытается создать экземпляр Tuple<S&>, и он генерирует следующие два объявления:

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& соответствует обоим. Эту проблему можно решить, разрушив каждый тип перед сохранением его в кортеж:

template <typename Head, typename... Tail>
Tuple<std::decay_t<Head>, std::decay_t<Tail>...> MakeTuple(Head&& head, Tail&&... tail);
0 голосов
/ 18 июня 2020

На мой вопрос был дан ответ выше, но я подумал, что было бы полезно включить полный код решения. Теперь он работает для всех типов, кроме массивов и строковых литералов.

#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;
}
...