program tip

initializer_list 및 이동 의미 체계

radiobox 2020. 9. 17. 07:29
반응형

initializer_list 및 이동 의미 체계


요소를 외부로 이동할 수 std::initializer_list<T>있습니까?

#include <initializer_list>
#include <utility>

template<typename T>
void foo(std::initializer_list<T> list)
{
    for (auto it = list.begin(); it != list.end(); ++it)
    {
        bar(std::move(*it));   // kosher?
    }
}

때문에 std::intializer_list<T>특별한 컴파일러주의가 필요하며 C ++ 표준 라이브러리의 일반 컨테이너와 같은 값의 의미가없는, 내가 오히려 미안한 것보다 안전한 물어 것입니다.


아니요, 의도 한대로 작동하지 않습니다. 당신은 여전히 ​​사본을 얻을 것입니다. 나는 이것이 'd' initializer_list가 될 때까지 일시적인 배열을 유지하기 위해 존재 한다고 생각했기 때문에 꽤 놀랐습니다 move.

beginend에 대한 initializer_list반환 const T *의 결과 때문에 move코드에서입니다 T const &&- 불변를 rvalue 참조. 그러한 표현은 의미있게 이동할 수 없습니다. T const &rvalue는 const lvalue 참조에 바인딩되기 때문에 형식의 함수 매개 변수에 바인딩되며 여전히 복사 의미 체계를 볼 수 있습니다.

아마도 그 이유는 컴파일러가 initializer_list정적으로 초기화 된 상수 를 만들도록 선택할 수 있기 때문일 것입니다. 그러나 유형을 만드는 것이 더 깨끗 initializer_list하거나 const initializer_list컴파일러의 재량에 따라 사용자가 어떤 것을 기대할 것인지 const변경 가능한 것인지 알지 못합니다. begin및의 결과입니다 end. 그러나 그것은 단지 내 직감입니다. 아마도 내가 틀린 이유가있을 것입니다.

업데이트 : 이동 전용 유형 지원을 위한 ISO 제안작성 했습니다initializer_list . 초안 일 뿐이며 아직 어디에도 구현되지 않았지만 문제에 대한 자세한 분석을 위해 볼 수 있습니다.


bar(std::move(*it));   // kosher?

당신이 의도 한 방식이 아닙니다. const개체를 이동할 수 없습니다 . 그리고 해당 요소에 std::initializer_list대한 const액세스 만 제공합니다 . 따라서 유형은 it입니다 const T *.

호출 std::move(*it)시도 하면 l- 값만 생성됩니다. IE : 사본.

std::initializer_list정적 메모리를 참조 합니다. 그것이 수업의 목적입니다. 움직임 은 그것을 변경하는 것을 의미하기 때문에 정적 메모리에서 이동할 수 없습니다 . 당신은 그것에서만 복사 할 수 있습니다.


이것은 list.begin()유형이 있기 때문에 명시된대로 작동하지 않으며 const T *상수 객체에서 이동할 수있는 방법이 없습니다. 언어 디자이너는 아마도 초기화 목록에 예를 들어 문자열 상수를 포함 할 수 있도록 그렇게 만들었을 것입니다.

그러나 이니셜 라이저 목록에 rvalue 표현식이 포함되어 있다는 것을 알고있는 경우 (또는 사용자가이를 작성하도록 강제하려는 경우) 작동하도록하는 트릭이 있습니다 (Sumant의 답변에서 영감을 얻었습니다). 그러나 해결책은 그보다 훨씬 간단합니다). 이니셜 라이저 목록에 저장된 요소는 T값이 아니라을 캡슐화하는 값 이어야 합니다 T&&. 그런 다음 해당 값 자체가 const한정된 경우에도 수정 가능한 rvalue를 검색 할 수 있습니다.

template<typename T>
  class rref_capture
{
  T* ptr;
public:
  rref_capture(T&& x) : ptr(&x) {}
  operator T&& () const { return std::move(*ptr); } // restitute rvalue ref
};

이제 initializer_list<T>인수 를 선언하는 대신 인수를 선언합니다 initializer_list<rref_capture<T> >. 다음은 std::unique_ptr<int>이동 의미론 만 정의 된 스마트 포인터 벡터를 포함하는 구체적인 예입니다 (따라서 이러한 객체 자체는 초기화 목록에 저장 될 수 없습니다). 그러나 아래 이니셜 라이저 목록은 문제없이 컴파일됩니다.

#include <memory>
#include <initializer_list>
class uptr_vec
{
  typedef std::unique_ptr<int> uptr; // move only type
  std::vector<uptr> data;
public:
  uptr_vec(uptr_vec&& v) : data(std::move(v.data)) {}
  uptr_vec(std::initializer_list<rref_capture<uptr> > l)
    : data(l.begin(),l.end())
  {}
  uptr_vec& operator=(const uptr_vec&) = delete;
  int operator[] (size_t index) const { return *data[index]; }
};

int main()
{
  std::unique_ptr<int> a(new int(3)), b(new int(1)),c(new int(4));
  uptr_vec v { std::move(a), std::move(b), std::move(c) };
  std::cout << v[0] << "," << v[1] << "," << v[2] << std::endl;
}

한 가지 질문에 답이 필요합니다. 이니셜 라이저 목록의 요소가 참 prvalue (예 : xvalue) 여야하는 경우 언어는 해당 임시의 수명이 사용되는 지점까지 확장되도록 보장합니까? 솔직히, 표준의 관련 섹션 8.5가이 문제를 전혀 다루지 않는다고 생각합니다. 그러나 1.9 : 10을 읽으면 모든 경우에 관련된 full-expression 이 이니셜 라이저 목록의 사용을 포함하는 것처럼 보이 므로 rvalue 참조를 매달리는 위험이 없다고 생각합니다.


해결 방법에 대한 합리적인 시작점을 제공하는 것이 유익 할 것이라고 생각했습니다.

주석 인라인.

#include <memory>
#include <vector>
#include <array>
#include <type_traits>
#include <algorithm>
#include <iterator>

template<class Array> struct maker;

// a maker which makes a std::vector
template<class T, class A>
struct maker<std::vector<T, A>>
{
  using result_type = std::vector<T, A>;

  template<class...Ts>
  auto operator()(Ts&&...ts) const -> result_type
  {
    result_type result;
    result.reserve(sizeof...(Ts));
    using expand = int[];
    void(expand {
      0,
      (result.push_back(std::forward<Ts>(ts)),0)...
    });

    return result;
  }
};

// a maker which makes std::array
template<class T, std::size_t N>
struct maker<std::array<T, N>>
{
  using result_type = std::array<T, N>;

  template<class...Ts>
  auto operator()(Ts&&...ts) const
  {
    return result_type { std::forward<Ts>(ts)... };
  }

};

//
// delegation function which selects the correct maker
//
template<class Array, class...Ts>
auto make(Ts&&...ts)
{
  auto m = maker<Array>();
  return m(std::forward<Ts>(ts)...);
}

// vectors and arrays of non-copyable types
using vt = std::vector<std::unique_ptr<int>>;
using at = std::array<std::unique_ptr<int>,2>;


int main(){
    // build an array, using make<> for consistency
    auto a = make<at>(std::make_unique<int>(10), std::make_unique<int>(20));

    // build a vector, using make<> because an initializer_list requires a copyable type  
    auto v = make<vt>(std::make_unique<int>(10), std::make_unique<int>(20));
}

It seems not allowed in the current standard as already answered. Here is another workaround to achieve something similar, by defining the function as variadic instead of taking an initializer list.

#include <vector>
#include <utility>

// begin helper functions

template <typename T>
void add_to_vector(std::vector<T>* vec) {}

template <typename T, typename... Args>
void add_to_vector(std::vector<T>* vec, T&& car, Args&&... cdr) {
  vec->push_back(std::forward<T>(car));
  add_to_vector(vec, std::forward<Args>(cdr)...);
}

template <typename T, typename... Args>
std::vector<T> make_vector(Args&&... args) {
  std::vector<T> result;
  add_to_vector(&result, std::forward<Args>(args)...);
  return result;
}

// end helper functions

struct S {
  S(int) {}
  S(S&&) {}
};

void bar(S&& s) {}

template <typename T, typename... Args>
void foo(Args&&... args) {
  std::vector<T> args_vec = make_vector<T>(std::forward<Args>(args)...);
  for (auto& arg : args_vec) {
    bar(std::move(arg));
  }
}

int main() {
  foo<S>(S(1), S(2), S(3));
  return 0;
}

Variadic templates can handle r-value references appropriately, unlike initializer_list.

In this example code, I used a set of small helper functions to convert the variadic arguments into a vector, to make it similar to the original code. But of course you can write a recursive function with variadic templates directly instead.


I have a much simpler implementation that makes use of a wrapper class which acts as a tag to mark the intention of moving the elements. This is a compile-time cost.

The wrapper class is designed to be used in the way std::move is used, just replace std::move with move_wrapper, but this requires C++17. For older specs, you can use an additional builder method.

You'll need to write builder methods/constructors that accept wrapper classes inside initializer_list and move the elements accordingly.

If you need some elements to be copied instead of being moved, construct a copy before passing it to initializer_list.

The code should be self-documented.

#include <iostream>
#include <vector>
#include <initializer_list>

using namespace std;

template <typename T>
struct move_wrapper {
    T && t;

    move_wrapper(T && t) : t(move(t)) { // since it's just a wrapper for rvalues
    }

    explicit move_wrapper(T & t) : t(move(t)) { // acts as std::move
    }
};

struct Foo {
    int x;

    Foo(int x) : x(x) {
        cout << "Foo(" << x << ")\n";
    }

    Foo(Foo const & other) : x(other.x) {
        cout << "copy Foo(" << x << ")\n";
    }

    Foo(Foo && other) : x(other.x) {
        cout << "move Foo(" << x << ")\n";
    }
};

template <typename T>
struct Vec {
    vector<T> v;

    Vec(initializer_list<T> il) : v(il) {
    }

    Vec(initializer_list<move_wrapper<T>> il) {
        v.reserve(il.size());
        for (move_wrapper<T> const & w : il) {
            v.emplace_back(move(w.t));
        }
    }
};

int main() {
    Foo x{1}; // Foo(1)
    Foo y{2}; // Foo(2)

    Vec<Foo> v{Foo{3}, move_wrapper(x), Foo{y}}; // I want y to be copied
    // Foo(3)
    // copy Foo(2)
    // move Foo(3)
    // move Foo(1)
    // move Foo(2)
}

Consider the in<T> idiom described on cpptruths. The idea is to determine lvalue/rvalue at run-time and then call move or copy-construction. in<T> will detect rvalue/lvalue even though the standard interface provided by initializer_list is const reference.

참고URL : https://stackoverflow.com/questions/8193102/initializer-list-and-move-semantics

반응형