web-dev-qa-db-ja.com

評価されていないコンテキストでのデフォルトのテンプレートパラメータとラムダ:バグか機能か?

まったく同じ構文を使用して、2つの異なるタイプを作成する目標を検討します。これはラムダで簡単に行うことができます:

auto x = []{};
auto y = []{};
static_assert(!std::is_same_v<decltype(x), decltype(y)>);

しかし、ラムダを使用する代わりに、別のよりエレガントな構文を探しています。ここにいくつかのテストがあります。まず、いくつかのツールを定義します。

#include <iostream>
#include <type_traits>
#define macro object<decltype([]{})>
#define singleton object<decltype([]{})>

constexpr auto function() noexcept
{
    return []{};
}

template <class T = decltype([]{})>
constexpr auto defaulted(T arg = {}) noexcept
{
    return arg;
}

template <class T = decltype([]{})>
struct object
{
    constexpr object() noexcept {}
};

template <class T>
struct ctad
{
    template <class... Args>
    constexpr ctad(const Args&...) noexcept {}
};

template <class... Args>
ctad(const Args&...) -> ctad<decltype([]{})>;

および次の変数:

// Lambdas
constexpr auto x0 = []{};
constexpr auto y0 = []{};
constexpr bool ok0 = !std::is_same_v<decltype(x0), decltype(y0)>;

// Function
constexpr auto x1 = function();
constexpr auto y1 = function();
constexpr bool ok1 = !std::is_same_v<decltype(x1), decltype(y1)>;

// Defaulted
constexpr auto x2 = defaulted();
constexpr auto y2 = defaulted();
constexpr bool ok2 = !std::is_same_v<decltype(x2), decltype(y2)>;

// Object
constexpr auto x3 = object();
constexpr auto y3 = object();
constexpr bool ok3 = !std::is_same_v<decltype(x3), decltype(y3)>;

// Ctad
constexpr auto x4 = ctad();
constexpr auto y4 = ctad();
constexpr bool ok4 = !std::is_same_v<decltype(x4), decltype(y4)>;

// Macro
constexpr auto x5 = macro();
constexpr auto y5 = macro();
constexpr bool ok5 = !std::is_same_v<decltype(x5), decltype(y5)>;

// Singleton
constexpr singleton x6;
constexpr singleton y6;
constexpr bool ok6 = !std::is_same_v<decltype(x6), decltype(y6)>;

そして次のテスト:

int main(int argc, char* argv[])
{
    // Assertions
    static_assert(ok0); // lambdas
    //static_assert(ok1); // function
    static_assert(ok2); // defaulted function
    static_assert(ok3); // defaulted class
    //static_assert(ok4); // CTAD
    static_assert(ok5); // macro
    static_assert(ok6); // singleton (macro also)

    // Display
    std::cout << ok1 << std::endl;
    std::cout << ok2 << std::endl;
    std::cout << ok3 << std::endl;
    std::cout << ok4 << std::endl;
    std::cout << ok5 << std::endl;
    std::cout << ok6 << std::endl;

    // Return
    return 0;
}

これは、オプション-std=c++2aを使用して、GCCの現在のトランクバージョンでコンパイルされます。コンパイラエクスプローラーの結果 ここ を参照してください。


ok0ok5およびok6が機能するという事実は、実際には驚くことではありません。しかし、ok2ok3trueであるのに対し、ok4はそうではないという事実は、私にとって非常に驚くべきことです。

  • 誰かがok3trueok4falseにするルールの説明を提供できますか?
  • それは本当にそれがどのように機能するか、またはこれは実験的な機能(評価されていないコンテキストのラムダ)に関するコンパイラのバグですか? (標準またはC++の提案への参照は大歓迎です)

注:これがバグではなく機能であることを本当に望んでいますが、それがいくつかのクレイジーなアイデアを実装可能にするためです

39
Vincent

誰かがok3をtrue、ok4をfalseにするルールの説明を提供できますか?

デフォルトの型としてラムダ型を使用するため、ok3はtrueです。

ラムダ式のタイプ(これはクロージャーオブジェクトのタイプでもあります)一意です、名前のない非共用体クラス型、

したがって、objectのデフォルトのテンプレートタイプ、macroおよびsingltoneのテンプレートパラメータタイプは、すべてのインスタンス化の後で常に異なります。ただし、関数functionの呼び出しで返されるラムダは一意であり、その型は一意です。テンプレート関数ctadには、パラメーターのテンプレートのみがありますが、戻り値は一意です。関数を次のように書き換えた場合:

template <class... Args, class T =  decltype([]{})>
ctad(const Args&...) -> ctad<T>;

この場合、戻り値の型はインスタンス化のたびに異なります。

2
Andrey Sv

ok2関数のパラメータータイプ(T)は、指定したテンプレートパラメーターによって異なります。 ok3のctorはテンプレートではありません。

ok4の場合、両方の控除は同じパラメーター型リスト(この場合は空)に依存し、そのため、控除は1回だけ発生します。テンプレートのインスタンス化と控除は異なります。同じパラメータータイプのリストの場合、推定は1回だけ行われますが、インスタンス化はすべての使用法で行われます。

このコードを見てください( https://godbolt.org/z/ph1Wk2 )。控除のパラメーターが異なる場合、別の控除が発生します。

0
mfurkanuslu