無害な範囲ベースのforループが機能しない


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

ゴッドボルトで試してみてください

コンパイラエラーは次のとおりです。 error: assignment of read-only reference 's'

今私の実際の場合、リストはクラスのメンバー変数で構成されています。

これで、式がinitializer_list<int>実際にa、b、c、およびdをコピーするになるため、これは機能しません。したがって、変更もできません。

私の質問は2つあります。

このように範囲ベースの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

私がおそらく行うことは、変数を1つずつ明示的に初期化することです。書くことはこれ以上ありません。明確で明示的であり、意図したとおりに機能します。:)
一部のプログラマー、

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

範囲は人々が望むほど魔法ではありません。最後に、コンパイラは、メンバ関数やfree関数のいずれかへの呼び出しを生成できるオブジェクトがなければならない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を使用し、可変テンプレートを使用しませんでした)
darune

4

標準§11.6.4List-initialization / p5 [dcl.init.list] [ Emphasis Mine ]によると:

タイプ 'std :: initializer_list'のオブジェクトは、実装が「array of N const E」タイプのprvalueを生成および実体した場合と同様に、イニシャライザリストから構築されます Nはイニシャライザリストの要素数)。その配列の各要素は、初期化子リストの対応する要素でコピー初期化され、std :: initializer_listオブジェクトはその配列を参照するように構築されます。[注:コピー用に選択されたコンストラクターまたは変換関数は、初期化子リストのコンテキストでアクセス可能でなければなりません(条項14)。— end note]いずれかの要素を初期化するためにナローイング変換が必要な場合、プログラムの形式が正しくありません。

したがって、コンパイラは合法的に文句を言います(つまり、範囲のforループで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

また、あなたの引用は私の質問に答えようとするものではありません
ダルネ

1
@darune-実際、引用符はあなたfor (auto& s : {a, b, c, d})が機能しない理由です。標準にその条項がある理由については、標準化委員会のメンバーに尋ねる必要があります。そのような多くのものと同様に、推論は、「特定のケースが気になるほど十分に有用であるとは考えていなかった」から「ケースをサポートするために変更する必要のある規格の他の部分が多すぎて、検討を延期した」までの間のいずれかになります。将来の標準を開発するまでのすべて」
Peter

そのまま使えないの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.