Как можно по умолчанию использовать специальную функцию-член, если она не знает типы ее параметров? - PullRequest
4 голосов
/ 29 мая 2011

Рассмотрим этот случай:

template<typename T>
struct A {
  A(A ???&) = default;
  A(A&&) { /* ... */ }
  T t;
};

Я явно объявил конструктор перемещения, поэтому мне нужно явно объявить конструктор копирования, если я хочу иметь конструктор без удаления копии.Если я хочу default это, как я могу найти правильный тип параметра?

A(A const&) = default; // or
A(A &) = default; // ?

Меня также интересует, сталкивались ли вы со случаем, когда такой сценарий действительно появлялся в реальных программах.Спецификация гласит:

Функция с явным значением по умолчанию должна ...

  • иметь тот же объявленный тип функции (за исключением, возможно, различных квалификаторов ссылок и за исключением того, что вВ случае конструктора копирования или оператора назначения копирования тип параметра может быть «ссылкой на неконстантный T», где T - имя класса функции-члена), как если бы он был неявно объявлен,

Если неявно объявленный конструктор копирования будет иметь тип A &, я хочу, чтобы мой конструктор копирования был явно установлен по умолчанию с параметром типа A &.Но если неявно объявленный конструктор копирования будет иметь тип параметра A const&, я не хочу, чтобы у моего явно дефолтного конструктора копирования был тип параметра A &, потому что это запретило бы копирование из константных значений.

Я не могу объявить обе версии, потому что это нарушит вышеприведенное правило для случая, когда неявно объявленная функция будет иметь тип параметра A &, а мое явно установленное по умолчанию объявление имеет тип параметра A const&.Из того, что я вижу, разница допустима только тогда, когда неявное объявление будет A const&, а явное объявление будет A &.

Редактировать: На самом деле, спецификация гласит:

Если функция имеет явное значение по умолчанию при первом объявлении, ...

  • вв случае конструктора копирования, конструктора перемещения, оператора копирования или оператора назначения перемещения он должен иметь такой же тип параметра, как если бы он был неявно объявлен.

Так что мне нужноопределить их вне класса (что, я думаю, не повредит, поскольку, насколько я вижу, единственное отличие состоит в том, что функция станет нетривиальной, что в любом случае, вероятно, так или иначе)

template<typename T>
struct A {
  A(A &);
  A(A const&);
  A(A&&) { /* ... */ }
  T t;
};

// valid!?
template<typename T> A<T>::A(A const&) = default;
template<typename T> A<T>::A(A &) = default;

Хорошо, я обнаружил, что это недопустимо, если явно объявленная функция равна A const&, тогда как неявное объявление будет A &:

Предоставленная пользователем явно дефолтная функция (т. Е.явно по умолчанию после его первого объявления) определяется в точке, где оно явно по умолчанию;если такая функция неявно определена как удаленная, программа является некорректной.

Это соответствует тому, что делает GCC.Теперь, как я могу достичь своей первоначальной цели соответствия типа неявно объявленного конструктора?

Ответы [ 3 ]

4 голосов
/ 29 мая 2011

Полагаю, я не вижу проблемы ... чем это отличается от обычного случая реализации конструктора копирования?

Если ваш конструктор копирования не изменит аргумент (и неявно определенный конструктор копирования)не буду этого делать), тогда аргумент должен быть передан как постоянная ссылка.Единственный известный мне случай использования конструктора копирования, который не принимает аргумент с помощью константной ссылки, - это когда в C ++ 03 вы хотите реализовать moving a la std::auto_ptr, что обычно в любом случае является плохой идеей,И в C ++ 0x перемещение будет реализовано так же, как и в конструкторе перемещения.

3 голосов
/ 29 мая 2011

Мне кажется, что вам понадобится некоторая дедукция типов (понятия?).

Компилятор обычно использует версию A(A const&), если только один из членов не требует, чтобы она была написана A(A&).Таким образом, мы могли бы обернуть небольшой шаблонный хакер, чтобы проверить, какая версия конструктора копирования у каждого члена.

Последний

Обратитесь к нему по адресу ideone , или прочитайте ошибки Clang после фрагмента кода.

#include <memory>
#include <type_traits>

template <bool Value, typename C>
struct CopyConstructorImpl { typedef C const& type; };

template <typename C>
struct CopyConstructorImpl<false,C> { typedef C& type; };

template <typename C, typename T>
struct CopyConstructor {
  typedef typename CopyConstructorImpl<std::is_constructible<T, T const&>::value, C>::type type;
};

// Usage
template <typename T>
struct Copyable {
  typedef typename CopyConstructor<Copyable<T>, T>::type CopyType;

  Copyable(): t() {}

  Copyable(CopyType) = default;

  T t;
};

int main() {
  {
    typedef Copyable<std::auto_ptr<int>> C;
    C a; C const b;
    C c(a); (void)c;
    C d(b); (void)d;  // 32
  }
  {
    typedef Copyable<int> C;
    C a; C const b;
    C c(a); (void)c;
    C d(b); (void)d;
  }
}

, что дает:

6167745.cpp:32:11: error: no matching constructor for initialization of 'C' (aka 'Copyable<std::auto_ptr<int> >')
        C d(b); (void)d;
          ^ ~
6167745.cpp:22:7: note: candidate constructor not viable: 1st argument ('const C' (aka 'const Copyable<std::auto_ptr<int> >')) would lose const qualifier
      Copyable(CopyType) = default;
      ^
6167745.cpp:20:7: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
      Copyable(): t() {}
      ^
1 error generated.

До издания

Вот лучшее, что я мог придумать:

#include <memory>
#include <type_traits>

// Usage
template <typename T>
struct Copyable
{
  static bool constexpr CopyByConstRef = std::is_constructible<T, T const&>::value;
  static bool constexpr CopyByRef = !CopyByConstRef && std::is_constructible<T, T&>::value;

  Copyable(): t() {}

  Copyable(Copyable& rhs, typename std::enable_if<CopyByRef>::type* = 0): t(rhs.t) {}
  Copyable(Copyable const& rhs, typename std::enable_if<CopyByConstRef>::type* = 0): t(rhs.t) {}

  T t;
};

int main() {
  {
    typedef Copyable<std::auto_ptr<int>> C; // 21
    C a; C const b;                         // 22
    C c(a); (void)c;                        // 23
    C d(b); (void)d;                        // 24
  }
  {
    typedef Copyable<int> C;                // 27
    C a; C const b;                         // 28
    C c(a); (void)c;                        // 29
    C d(b); (void)d;                        // 30
  }
}

Что почти работает ... за исключением того, что я получил некоторые ошибки при построении "a".

6167745.cpp:14:78: error: no type named 'type' in 'std::enable_if<false, void>'
      Copyable(Copyable const& rhs, typename std::enable_if<CopyByConstRef>::type* = 0): t(rhs.t) {}
                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
6167745.cpp:22:11: note: in instantiation of template class 'Copyable<std::auto_ptr<int> >' requested here
        C a; C const b;
          ^

И:

6167745.cpp:13:67: error: no type named 'type' in 'std::enable_if<false, void>'
      Copyable(Copyable& rhs, typename std::enable_if<CopyByRef>::type* = 0): t(rhs.t) {}
                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
6167745.cpp:28:11: note: in instantiation of template class 'Copyable<int>' requested here
        C a; C const b;
          ^

Оба происходят по одной и той же причине, и я не понимаю, почему.Кажется, что компилятор пытается реализовать все конструкторы, хотя у меня есть конструктор по умолчанию.Я бы подумал, что SFINAE будет применяться, но, похоже, это не так.

Однако строка ошибки 24 обнаружена правильно:

6167745.cpp:24:11: error: no matching constructor for initialization of 'C' (aka 'Copyable<std::auto_ptr<int> >')
        C d(b); (void)d;
          ^ ~
6167745.cpp:13:7: note: candidate constructor not viable: 1st argument ('const C' (aka 'const Copyable<std::auto_ptr<int> >')) would lose const qualifier
      Copyable(Copyable& rhs, typename std::enable_if<CopyByRef>::type* = 0): t(rhs.t) {}
      ^
6167745.cpp:11:7: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
      Copyable(): t() {}
      ^

Где мы можем видеть, что CopyByConstRef был правильноизгнан из перегрузки, надеюсь, благодаря SFINAE.

1 голос
/ 29 мая 2011

Я никогда не видел случая, когда неявный конструктор копирования был бы A& - вы должны быть хороши в любом случае с const A&.

...