std:: альтернативы initializer_list для не копируемых типов

#c #c 11 #list-initialization

#c #c 11 #список-инициализация

Вопрос:

Я знаю, что попытка использовать a std::initializer_list<NonCopyable> приводит к ошибке, потому что элементы копируются во временный массив, представленный initializer_list . Я также прочитал некоторое объяснение того, почему было бы неправильно иметь ссылки на rvalue в списке, с чем меня все устраивает.

Проблема в том, что я хотел бы передавать не копируемые объекты не для того, чтобы перейти от них, а только const для доступа к ним, поэтому аргумент о rvalues неприменим. Что я могу сделать, чтобы сохранить, по возможности, синтаксис инициализации списка и ссылочную семантику (без оболочек, без необработанных указателей)?

 NonCopyable a{...}, b{...};
ListInitialized c{a, b};
  

Я думаю, что здесь я упускаю что-то чрезвычайно очевидное.

Обновить:

Это работает (*),

 ListInitialized(std::initializer_list<std::reference_wrapper<NonCopyable>>) {...}
  

но не будет принимать rvalues. Было бы неплохо, если бы я мог просто передать список всего, что могло бы войти в const NonCopyableamp; .

(*) Я знаю, что написал «без оболочек», но это не влияет ни на вызывающий код, ни на итерацию по списку.

Комментарии:

1. Возможно, я что-то неправильно понял, но почему не просто std::initializer_list<std::reference_wrapper<const NonCopyable>> ?

2. @MichaelKenzel const или нет, он не будет принят ListInitialized c{NonCopyable{...}} . Если бы T можно было копировать, я мог бы свободно использовать существующие объекты типа T или сразу созданные, или смешивать и сопоставлять.

3. Действительно, я забыл, что это std::reference_wrapper мешает вам использовать его со ссылками на rvalue. Вы могли бы запустить свою собственную вспомогательную функцию преобразования, например, std::reference_wrapper<const T> cref(Tamp;amp; r) { return r; } а затем выполнить ListInitialized { cref(NonCopyable{...}) } . Но я бы, вероятно, просто выбрал подход, описанный Angew ниже…

Ответ №1:

Вы можете предоставить ListInitialized шаблон конструктора с переменными значениями:

 struct ListInitialized
{
  template <class... T>
  ListInitialized(const T... amp;arg);
};
  

Если вам нужно убедиться, что его экземпляр может быть создан только с правильным типом, рассмотрите подходящие SFINAE:

 struct ListInitialized
{
  template <
    class... T,
    class Sfinae = std::enable_if_t<std::is_same<std::decay_t<T>, NonCopyable> amp;amp;...
  >
  ListInitialized(const T... amp;arg);
};
  

Комментарии:

1. Хороший подход! Это каким-то образом противоречит намерению std::initializer_list , хотя?

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

Ответ №2:

В дополнение к комментариям и ответам выше, я обнаружил, что эта минималистичная оболочка удовлетворяет моим потребностям:

 #include <initializer_list>
#include <utility>

struct S {
  S() { }
  S(const Samp;) = delete; // Non-copyable
  void f() const { }
};

template<class T>
class const_reference_wrapper {
public:
  const_reference_wrapper(const Tamp; ref_) : ref(ref_) { }
  operator const Tamp;() const { return ref; }
private:
  const Tamp; ref;
};

struct T {
  T(std::initializer_list<const_reference_wrapper<S>> l) : c(l.size()) {
    for(const Samp; i : l)  // note: const autoamp; can't be used here, but it would be the same for std::reference_wrapper
      i.f();  // we can do something with the elements
  }

  int c;
};

int main() {
  S a, b;
  T t{a, b, S{}};  // we can mix lvalues and rvalues with a natural syntax
  return t.c;  // correctly returns 3
}
  

Конечно, необходимо позаботиться о том, чтобы любое rvalue, переданное через это, сохранялось в течение всего времени, когда на него ссылаются.