boost.hanaで「constexpr以外の変数 'a'の読み取りは定数式では許可されない」という問題を解決する方法


8

私はいくつかのメタプログラミングプログラムを書くためにBoost.hanaでc ++ 17を使用しています。私を悩ませた1つの問題は、static_assertのようなconstexprコンテキストで使用できる式の種類です。次に例を示します。

#include <boost/hana.hpp>

using namespace boost::hana::literals;

template <typename T>
class X {
public:
    T data;

    constexpr explicit X(T x) : data(x) {}

    constexpr T getData() {
        return data;
    }
};


int main() {
    {   // test1
        auto x1 = X(1_c);
        static_assert(x1.data == 1_c);
        static_assert(x1.getData() == 1_c);
    }
    {   //test2.1
        auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
        static_assert(x2.data[0_c] == 1_c);

        // static_assert(x2.getData()[0_c] == 1_c); // read of non-constexpr variable 'x2' is not allowed in a constant expression
    }
    {   //test2.2
        auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
        auto data = x2.getData();
        static_assert(data[0_c] == 1_c);
    }
}

まず、フィールドデータとアクセサーgetData()を使用してクラスXを記述します。でメイン()TEST1の一部、x1.data及びx1.getData()私は予想通り同じ挙動。しかし、中にTEST2の一部、ブースト::花のタプルの引数を変更し、まだ罰金に動作しますが、「のエラーで、コンパイルを失敗X2非constexprの変数の読み込み『』の定数式では許可されていません」。私は分割する場合はどうweiredことはありにし、それがうまく再コンパイルします。彼らは同じように振る舞うと思います。それで、誰もがこの例のstatic_assertで使用できない理由を説明するのを助けることができますか?static_assert(x2.data[0_c] == 1_c)static_assert(x2.getData()[0_c] == 1_c)x2.getData()[0_c]auto data = x2.getData();static_assert(data[0_c] == 1_c);x2.getData()[0_c]

再現するには:clang ++ 8.0 -I / path / to / hana-1.5.0 / include -std = c ++ 17 Test.cpp


興味深いことに、GCCは正常にコンパイルします。私はコードをこのより短いものに減らしました。
xskxzr

constexpr上で不足しているx2dataconstgetDatagodbolt.org/z/ZNL2BK
Maxim Egorushkin

@xskxzr残念ながら、ターゲットに使用できるのはClangだけです。
長い

回答:


5

問題はboost::hana::tuple、コピーコンストラクターがないことです。

それは持っているコンストラクタというルックスコピーコンストラクタのように:

template <typename ...dummy, typename = typename std::enable_if<
    detail::fast_and<BOOST_HANA_TT_IS_CONSTRUCTIBLE(Xn, Xn const&, dummy...)...>::value
>::type>
constexpr tuple(tuple const& other)
    : tuple(detail::from_index_sequence_t{},
            std::make_index_sequence<sizeof...(Xn)>{},
            other.storage_)
{ }

ただし、これはテンプレートであるため、コピーコンストラクタではありません

以来boost::hana::tupleコピーコンストラクタを持っていない、1がされて暗黙的に宣言不履行として(以降、それは抑制されないと規定されboost::hana::tupleていない任意のコピーや移動コンストラクタや代入演算子をあなたはそれを推測し、ので、彼らは、テンプレートにはできません)。

ここでは、次のプログラムの動作で示される実装の相違を確認します。

struct A {
    struct B {} b;
    constexpr A() {};
    // constexpr A(A const& a) : b{a.b} {}    // #1
};
int main() {
    auto a = A{};
    constexpr int i = (A{a}, 0);
}

gccは受け入れますが、ClangとMSVCは拒否しますが、行#1がコメント化されていない場合は受け入れます。つまり、コンパイラーは、非(直接)空のクラスの暗黙的に定義されたコピーコンストラクターが定数評価コンテキスト内での使用を許可されるかどうかについて意見が異なります。

暗黙的に定義されたコピーコンストラクターの定義によれば、constexpr A(A const&) = default;gccが正しいので#1がどのようにも異なることはありません。また、Bにユーザー定義のconstexprコピーコンストラクターClangとMSVCを再度受け入れると、これらのコンパイラーは、再帰的に空の暗黙的にコピー可能なクラスのconstexprコピーの構成可能性を追跡できないという問題があることに注意してください。MSVCおよびClangのバグを報告(Clang 11で修正)。

の使用operator[]はレッドニシンであることに注意してください。問題は、コンパイラーがなどの定数評価コンテキスト内でgetData()(コピーを構築するT)への呼び出しを許可するかどうかstatic_assertです。

明らかに、理想的なソリューションは、Boost.Hana boost::hana::tupleが実際のコピー/移動コンストラクターとコピー/移動割り当て演算子を持つように修正することです。(コードがユーザー提供のコピーコンストラクターを呼び出すため、これによりユースケースが修正されます。これは定数評価コンテキストで許可されています。)回避策として、getData()非ステートフルのケースを検出するためにハッキングを検討できますT

constexpr T getData() {
    if (data == T{})
        return T{};
    else
        return data;
}

デフォルトのコピーコンストラクターが機能しない理由がわかりません...もう一度説明してもらえますか?
アントワーヌモリア

@AntoineMorrierこれはclangのバグだと思います。再帰的に空のクラスをconstexprコンテキストでコピーできることを認識するのは難しいようです。
ecatmur

ありがとう。非常に役立つ回答です。ではtest2.2、Clangがその部分を受け入れた理由をさらに説明できますか?(元の質問を編集し、test2をtest2.1とtest2.2に分割しました)私はそれらが同じように動作することを期待していました。
ロング

@Clangが不満に思っているのは、hana::tupleからの戻りで発生するコピー構築ですgetData。test2.2では、コピーは定数評価コンテキストの外で行われるため、Clangは問題ありません。
ecatmur

えー...それは...少なくとも、私はそれを理解するために、少し難しいですgetData()...ここでは使用できなく、外に出て、その後temperalを導入し受け入れていない
ロング

1

問題は、ランタイム値を取得してコンパイル時にテストしようとしているためです。

あなたができることは、コンパイル時に式をaを通して強制することです、decltypeそしてそれは魅力のように動作します:)。

static_assert(decltype(x2.getData()[0_c]){} == 1_c);

#include <boost/hana.hpp>

using namespace boost::hana::literals;

template <typename T>
class X {
public:
    T data;

   constexpr explicit X(T x) : data(x) {}

   constexpr T getData() {
        return data;
    }
};


int main() {
    {   // test1
        auto x1 = X(1_c);
        static_assert(x1.data == 1_c);
        static_assert(x1.getData() == 1_c);
    }
    {   //test2
        auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
        static_assert(x2.data[0_c] == 1_c);

         static_assert(decltype(x2.getData()[0_c]){} == 1_c);

        auto data = x2.getData();
        static_assert(data[0_c] == 1_c);
    }
}

現在、式はコンパイル時に評価されるため、型はコンパイル時に認識され、計算時にも構築可能であるため、static_assert内で使用できます。


答えてくれてありがとう。を使用してstatic_assert(decltype(x2.getData()[0_c]){} == 1_c)うまくいくことは理解できますが、それでも大幅に節約できるdecltypeので、それでも保存したいと思います。私はあなたが言っていると思いx2.getData()、それはstatic_assert式では表示されませんので、実行時の値を取得しています。それから私はなぜ理解していないx1.getData()test1の一部であり、データdata[0_c]TEST2一部で、うまく動作することができます。それらの違いは何ですか?
長い

どのような場合でも、アクセスされる実行時の値は実際にはありませんが、おそらく標準では、コンパイラがそれをチェックすることを許可していません。
Jason Rice

0

したがって、まず、getData()メソッドにconst修飾子がないため、次のようになります。

constexpr T getData() const

constexprとしてマークされていない場合、少なくとも標準的な観点から、変数はconstexprに昇格されません。

これはhana :: integral_constantに特化したx1タイプである必要はないことに注意してください。X結果は1_cユーザー定義のコピーコンストラクターのないタイプであり、内部にデータが含まれていないため、コピー操作getData()は実際には何も行われません。、したがって、式: static_assert(x1.getData() == 1_c); は実際のコピーが行われていないため、問題ありません(または非const thisポインターへのアクセスもx1必要です)。

これは、フィールド内のデータからのhana::tuple実際のコピー構築を含むコンテナとは大きく異なります。これには、ポインタへの実際のアクセスが必要です。これは、constexpr変数でもなかったの場合は必要ありませんでした。hana::tuplex2.datathisx1

この手段は、あなたは両方のあなたの意図が間違っを発現していることx1x2、それは、少なくともために、必要であるx2constexprのように、これらの変数をマークするために、。また、基本的に空の(ユーザー定義のコピーコンストラクターがない)特殊化である空のタプルを使用しても、hana::tupleシームレスに機能することに注意してください(test3セクション)。

#include <boost/hana.hpp>

using namespace boost::hana::literals;

template <typename T>
class X {
public:
    T data;

    constexpr explicit X(T x) : data(x) {}

    constexpr T getData() const {
        return data;
    }
};

template<typename V>
constexpr auto make_X(V value)
{
    return value;
}

int main() {
    {   // test1
        auto x1 = X(1_c);
        static_assert(x1.data == 1_c);
        static_assert(x1.getData() == 1_c);
    }
    {   //test2
        constexpr auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
        static_assert(x2.data[0_c] == 1_c);

        static_assert(x2.getData()[0_c] == 1_c); // read of non-constexpr variable 'x2' is not allowed in a constant expression

        auto data = x2.getData();
        static_assert(data[0_c] == 1_c);
    }
    {   //test3
        auto x3 = X(boost::hana::make_tuple());
        static_assert(x3.data == boost::hana::make_tuple());

        static_assert(x3.getData() == boost::hana::make_tuple());
    }
}

私はまだすべての答えを読んでいませんが、constexprメソッドは通常constでない可能性があります。
アントワーヌモリエ

x1空の型だとは思わない。のインスタンスにXはデータメンバーがあります。またhana::tuple、空の型を含むこと自体は、空の基本最適化を使用するため、空です。Clangやlibc ++がでおかしなことをしている可能性があるため、コピーコンストラクターを非難している可能性がありますstd::integral_constant
Jason Rice

そして、x2宣言のためにconstexprを追加する必要がない方法はありますか?Xを定数値とランタイム値の両方で初期化できるようにしたいのですが。例: `` `int g = 1; int main(){{/ * test3 * / auto x3 = X(g); }} `` `完璧に機能することを願っています。しかし、X3にconstexprのを追加するとエラーが発生して、コンパイルされません:constexpr variable 'x3' must be initialized by a constant expression
ロング

@AntoineMorrier:はい。ただし、const thisポインターを使用していない限り問題ありませんx2。static_assertのケースで残念ながらそれを使用しています。(x1の場合-これはさらなる議論です:))
のMichałロス

@JasonRice:はい、そうです。私は正確ではなかったので、答えを微調整します。もちろん、どちらにも非静的フィールドはありません。それでも、これが私の答えに欠けhana::integral_constantていることですが、デフォルトのコンパイラー定義コンストラクターhana::tupleはありますが、ユーザー定義コンストラクターがあることに注意してください。すべてのコンストラクタを持たない空のタプルのための専門があるので、また、空のタプルのために同じコードが動作します。godbolt.org/z/ZeEVQN
のMichałLOS
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.