루프가 작동하지 않는 무고한 범위


11

다음은 컴파일 되지 않습니다 .

#include <iostream>

int main()
{
    int a{},b{},c{},d{};

    for (auto& s : {a, b, c, d}) {
        s = 1;
    }
    std::cout << a << std::endl;
    return 0;
}

Godbolt에서 사용해보십시오

컴파일러 오류는 다음과 같습니다 error: assignment of read-only reference 's'

이제 실제 경우에는 목록이 클래스의 멤버 변수로 구성됩니다.

이제는 표현식이 initializer_list<int>실제로 a, b, c 및 d를 복사하는 표현식이되기 때문에 작동하지 않습니다 . 따라서 수정도 허용하지 않습니다.

내 질문은 두 가지입니다.

이런 식으로 범위 기반 for 루프를 작성하지 못하게하는 동기가 있습니까? 예. 벌거 벗은 괄호 표현에는 특별한 경우가있을 수 있습니다.

이 유형의 루프를 고정 하는 구문적인 깔끔한 방법은 무엇입니까 ?

이 라인을 따르는 것이 선호됩니다.

for (auto& s : something(a, b, c, d)) {
    s = 1;
}

포인터 간접 참조를 좋은 솔루션이라고 생각하지 않습니다. 즉 {&a, &b, &c, &d}, 반복자가 참조 해제 될 때 모든 솔루션이 요소 참조를 직접 제공해야합니다 .


1
간단한 해결책 (실제로 사용하지 않을 것)은 대신 ​​포인터 목록을 만드는 것 { &a, &b, &c, &d }입니다.
일부 프로그래머 친구

2
initializer_list대부분 const배열에 대한 견해입니다 .
Jarod42

내가 할 일은 변수를 하나씩 명시 적으로 초기화하는 것입니다. 더 많이 쓰지 않을 것이며, 명확하고 명시 적이며 의도 한 것을 수행합니다. :)
일부 프로그래머 친구

3
당신이 원하지 않는다면 { &a, &b, &c, &d }, 당신도 원하지 않을 것입니다 :for (auto& s : std::initializer_list<std::reference_wrapper<int>>{a, b, c, d}) { s.get() = 1; }
Jarod42

"이것이 왜 작동하지 않는가"라는 질문은 "이것과 같은 것을 만들기 위해 어떻게해야합니까?"와는 매우 다른 질문입니다.
Nicol Bolas

답변:


4

범위는 사람들이 원하는만큼 마술이 아닙니다. 결국, 컴파일러가 멤버 함수 또는 자유 함수 begin()및에 대한 호출을 생성 할 수있는 오브젝트가 있어야합니다 end().

당신이 올 수있는 가장 가까운 것은 :

#include <iostream>

int main()
{
    int a{},b{},c{},d{};

    for (auto s : {&a, &b, &c, &d} ) {
        *s = 1;
    }
    std::cout << a << "\n";
    return 0;
}

1
당신은 떨어 뜨릴 수 있습니다 std::vector<int*>.
Jarod42

@mhhollomon 명시 적으로 포인터 간접 솔루션에 관심이 없다고 언급했습니다.
darune

1
그것은해야 auto s하거나 auto* s,하지 auto& s.
LF

@ darune-누군가 다른 답변을 드리겠습니다. 이러한 답변이 현재 표준에 존재하는지는 확실하지 않습니다.
mhhollomon

@LF-동의했습니다.
mhhollomon

4

래퍼 아이디어 내의 또 다른 솔루션 :

template<typename T, std::size_t size>
class Ref_array {
    using Array = std::array<T*, size>;

    class Iterator {
    public:
        explicit Iterator(typename Array::iterator it) : it_(it) {}

        void operator++() { ++it_; }
        bool operator!=(const Iterator& other) const { return it_ != other.it_; }
        decltype(auto) operator*() const { return **it_; }

    private:
        typename Array::iterator it_;
    };

public:
    explicit Ref_array(Array args) : args_(args) {}

    auto begin() { return Iterator(args_.begin()); }
    auto end() { return Iterator(args_.end()); }

private:
    Array args_;
};

template<typename T, typename... Ts>
auto something(T& first, Ts&... rest) {
    static_assert((std::is_same_v<T, Ts> && ...));
    return Ref_array<T, 1 + sizeof...(Ts)>({&first, &rest...});
}

그때:

int main() {
    int a{}, b{}, c{}, d{};

    for (auto& s : something(a, b, c, d)) {
        std::cout << s;
        s = 1;
    }

    std::cout  << std::endl;
    for (auto& s : something(a, b, c, d))
        std::cout << s;
}

출력

0000
1111

2
이것은 괜찮은 해결책 / 해결책입니다. 그것은 내 자신의 대답과 비슷한 아이디어입니다 (나는 std :: reference_wrapper를 사용하고 variadic 템플릿을 사용하지 않음)
darune

4

표준 §11.6.4 List-initialization / p5 [dcl.init.list] [ Emphasis Mine ] 에 따르면 :

'std :: initializer_list'유형의 객체는 구현이 "array of N const E"유형의 prvalue를 생성하고 구체화 한 것처럼 (7.4) 이니셜 라이저 목록에서 구성됩니다. 여기서 N은 초기화 목록의 요소 수입니다. 해당 배열의 각 요소는 초기화 목록의 해당 요소로 복사 초기화되고 std :: initializer_list 객체는 해당 배열을 참조하도록 구성됩니다. [참고 : 사본을 위해 선택된 생성자 또는 변환 함수는 이니셜 라이저 목록의 컨텍스트에서 액세스 할 수 있어야합니다 (Clause 14). — end note] 요소를 초기화하기 위해 축소 변환이 필요한 경우 프로그램이 잘못 구성됩니다.

따라서 컴파일러는 합법적으로 불평하고 있습니다 (즉, 범위가 지정된 루프에서 auto &s공제되어 int const& s할당 할 수 없습니다 s).

'std :: reference_wrapper'를 사용하여 초기화 목록 대신 컨테이너 (예 :`std :: vector ')를 도입하여이 문제를 완화 할 수 있습니다.

#include <iostream>
#include <vector>
#include <functional>

int main()
{
    int a{},b{},c{},d{};

    for (auto& s : std::vector<std::reference_wrapper<int>>{a, b, c, d}) {
        s.get()= 1;
    }
    std::cout << a << std::endl;
    return 0;
}

라이브 데모


@ Jarod42 Ouups 죄송합니다.
101010

당신의 솔루션은 좋은 솔루션에 대한 나의 기준에 맞지 않습니다-내가 만족하지 않았다면 내가 처음에 묻지 않았을 것입니다 :)
darune

또한 당신의 인용은 내 질문에 대답하지 않습니다
darune

1
@ darune-실제로 따옴표는 for (auto& s : {a, b, c, d})작동하지 않는 이유입니다 . 표준에 해당 조항이있는 이유에 대해서는 ..... 표준화위원회 구성원에게 문의해야합니다. 많은 다른 것들과 마찬가지로, 추론은 "귀하의 특정 사례가 귀찮게하기에 충분히 유용하다고 생각하지 않았다"에서 "표준의 다른 많은 부분이 귀하의 사례를 뒷받침하기 위해 변경되어야 할 것"사이에있을 수 있습니다. 미래 표준을 개발할 때까지
피터

그냥 사용할 수 std::array<std::reference_wrapper>>없습니까?
Toby Speight

1

그 구문을 만족시키기 위해

for (auto& s : something{a, b, c, d}) {
    s = 1;
}

랩퍼를 작성할 수 있습니다.

template <typename T>
struct MyRefWrapper
{
public:
    MyRefWrapper(T& p)  : p(&p) {}

    T& operator =(const T& value) const { return *p = value; }

    operator T& () const { return *p; }
private:
    T* p;     
};

데모


1
그것과 어떻게 다릅니 std::reference_wrapper까?
Toby Speight

1
@TobySpeight : std::reference_wrapper필요합니다 s.get() = 1;.
Jarod42

0

솔루션 : 참조 랩퍼 사용

template <class It>
struct range_view_iterator : public It{//TODO: don't inherit It
    auto& operator*() {
        return (*this)->get();
    }
};

template<class It>
range_view_iterator(It) -> range_view_iterator<It>;


template<class T>
struct range_view {
    std::vector<std::reference_wrapper<T> > refs_;
    range_view(std::initializer_list<std::reference_wrapper<T> > refs) : refs_{refs} {
    }

    auto begin() {
        return range_view_iterator{ refs_.begin() };
    }

    auto end() {
        return range_view_iterator{ refs_.end() };
    }
};

그런 다음 다음과 같이 사용하십시오.

for (auto& e : range_view<int>{a, b, c, d}) {
    e = 1;
}

그래도 첫 번째 질문에 대답하지는 않습니다.


-1

참조를 저장하기위한 랩퍼 클래스를 작성할 수 있으며이 값을 업데이트하기위한 지정 연산자가 있습니다.

template<class T>
struct Wrapper {
    T& ref;

    Wrapper(T& ref)
    : ref(ref){}

    template<class U>
    void operator=(U u) {
        ref = u;
    }
};

template<class...T>
auto sth(T&...t) {
    return std::array< Wrapper<std::common_type_t<T...> > ,sizeof...(t) >{Wrapper(t)...};
};

int main(){
    int a{},b{},c{},d{};

    for (auto s : sth(a,b,c,d)) {
        s = 1;
    }
    std::cout << a << std::endl; // 1

라이브 데모

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.