constexpr文字列リテラルチェック:短い構文、実行時の可能性なし

Aug 21 2020

編集:私の最終的な解決策は中毒法を使用していないため、名前が変更されました。

実行時にconstexprメソッドが呼び出されないようにする方法を探しています。文字列リテラルを受け入れる関数を書いているので、constexprパラメータを要求する方法としてNTTPを単純に使用することはできません。

template<const char* str>
auto func() {...}

その場合、正当なconstexprの使用でさえ面倒になり、値に静的リンケージが必要になり、文字列リテラルを入力できないためです。私はやってみたいです:

constexpr auto func(const char* str) {...}

その理由は、文字列を値のリストと照合し、パラメーターが許可されたセットに含まれていることを静的に確認したいためです。私はこれを簡単に行うことができます。関数をthrow()'ingするだけでconstexpr、コンパイル時エラーが発生する可能性があります。しかし、実行時にプログラムを終了させるブランチを使用して本番コードを生成する可能性は望んでいません。それは私の分野で大きな問題を引き起こすでしょう。この機能は、「その他の重要なこと」を実行するプログラムに便利であり、プログラムが終了すると悪いことが起こります。

私はこれを行うための可能な方法の全体について読んだ:

  1. C ++ 20を使用する-C ++ 20を使用constevalしない
  2. C ++ 20を使用する-C ++ 20を使用std::is_constant_evaluatedしない
  3. 結果を未定義のシンボル(たとえばextern int i、定義されないシンボル)に返すことにより、実行時にメソッドをポイズニングします。コンパイラは、メソッドがコンパイル時に呼び出された場合にそのシンボルを返すコードを作成することはありませんが、メソッドが実行時に呼び出された場合は作成し、リンクエラーが発生します。動作しますが、醜いリンカーエラー。私のお気に入りではありません。そのバージョンは、私の投稿に示されています。結果が無視された場合、コンパイル時にConstexpr関数は呼び出されません。
  4. C ++ 17では、コンテキストで実際に呼び出される関数のnoexcept呼び出しに自動的に追加されます。したがって、vs for (明示的に宣言されていない)を実行して、呼び出しが/ notになるかどうかを検出できます。ただし、一部のパラメーターを受け入れた関数内からこれを行うことはできません。呼び出しサイトから行う必要があります。したがって、おそらくヘルパーMACROが必要です(私のお気に入りではありませんが、機能します)。constexprconstexprnoexcept(foo(1))noexcept(foo(i))constexpr int foo(int i)noexcepticonstexprconstexpr
  5. ラムダのスコープ外の特定の変数がそうでない場合、戻り値の型が無効であるラムダを作成しますconstexpr。この投稿の詳細は次のとおりです。https://stackoverflow.com/a/40413051

だから私は#3または#4 +マクロを使うことに傾いていますが、***この投稿は#5 ***、またはまったく新しいアイデアについてです。

たとえば、ラムダなしで#5を実行する方法を誰かが思い付くことができますか?その後constexpr、呼び出しサイトから使用するのではなく、関数自体の中で使用する方法を考え出すことができるかどうかを確認したいと思います。今のところ、constexpr実行時に呼び出された場合は関数をポイズニングしてみてくださいconstexpr。関数呼び出しがであるかどうかを「検出」することは忘れてください。

main著者のようにラムダを作成することで#5の結果を再作成できますが、それは実際にはあまり有用ではなく、完全に合法であるとはまだ確信していません。まず、ラムダで実行できることはすべて、ラムダなしで実行できます-そうですか?ラムダなしでは、元の作成者のメソッドを機能させることすらできません。これは、の外部で機能させるために必要な最初のステップのようですmain()

以下は、ラムダなしで#5を再現しようとしたいくつかのアイデアです。10億以上の順列を持つ実例。どれも機能しません。https://onlinegdb.com/B1oRjpTGP

// Common
template<int>
using Void = void;

// Common
struct Delayer {
    constexpr auto delayStatic(int input) { return input; }
};

// Attempt 1
template<typename PoisonDelayer>
constexpr auto procurePoison(int i) {
    struct Poison {
        // error: use of parameter from containing function
        // constexpr auto operator()() const -> Void<(PoisonDelayer::delayStatic(i), 0)> {}
    } poison;
    
    return poison;
}

// Attempt 2
struct PoisonInnerTemplate {
    const int& _i;

    // Internal compiler error / use of this in a constexpr
    template<typename PoisonDelayer>
    auto drink() const -> Void<(PoisonDelayer::delayStatic(_i), 0)> {}
};

int main()
{
    auto attempt1 = procurePoison<Delayer>(1);
    
    constexpr int i = 1;
    auto attempt2 = PoisonInnerTemplate{i};
    attempt2.drink<Delayer>();

    return 0;
}

もう1つ、許可されたタグの事前定義されたリストを作成し(NTTPになるように文字列を構造体でラップする)、それらをある種のコンテナーに入れてから、取得するメソッドを用意するというアイデアをいじくりまわしました。それら。問題は次のとおりです。(1)呼び出しサイトの構文はそれらを使用するために非常に冗長になります。(2)呼び出しサイトが次のような構文を使用MyTags::TAG_ONEすることは問題ありませんが、私のプログラムは完全なセットを知ることができる必要があります。タグの数が多いため、配列またはテンプレート変数に含める必要があります。(3)配列要素を取得すると、rvalueリンケージのないが生成されるため、配列を使用しても機能しません。そのため、タグとしてフィードすることはできません。 NTTP、(4)明示的な特殊化を備えたテンプレート変数を使用して各タグを定義するには、テンプレート変数がグローバルスコープである必要がありますが、これは私にはうまく機能しません...

これは私ができる最善のことです-私はそれがちょっと醜いと思います...:

struct Tag {
    const char* name;
};

template<auto& tag>
void foo() {}

struct Tags {
    static constexpr Tag invalid = {};
    static constexpr Tag tags[] = {{"abc"}, {"def"}};

    template<size_t N>
    static constexpr Tag tag = tags[N];
    
    template<size_t N = 0>
    static constexpr auto& getTag(const char* name) {
        if constexpr(N<2) {
            if(string_view(name)==tag<N>.name) {
                return tag<N>;
            } else {
                return getTag<N+1>(name);
            }
        } else {
            return invalid;
        }
    }
};

int main()
{
    foo<Tags::getTag("abc")>();
}

回答

Sean Aug 24 2020 at 20:32

これが私自身の答えです。文字列リテラルがCOMPILE-TIMEで許可されたセット内にあることを確認し、その文字列の値に基づいてアクションを実行します。constexpr関数のポイズニングは必要ありません。また、文字列リテラルに静的リンケージを提供するための面倒な要件もありません。

クレジットはgcc、C ++ 20の一部であるがC ++ 17ではない文字列テンプレートのユーザー定義リテラルの拡張機能を使用する「短縮オプション2」のJarod42に与えられます。

3つの「省略形」の呼び出しサイト構文のいずれにも十分満足していると思います。私はまだ代替案や改善、または私が台無しにしたものへのポインタを歓迎します。完全な転送などは、読者の練習問題として残されています;-)

ライブデモ: https://onlinegdb.com/S1K_7sb7D

// Helper for Shorthand Option 1 (below)
template<typename Singleton>
Singleton* singleton;

// Helper to store string literals at compile-time
template<typename ParentDispatcher>
struct Tag {
    using Parent = ParentDispatcher;
    const char* name;
};

// ---------------------------------
// DISPATCHER:
// ---------------------------------
// Call different functions at compile-time based upon
// a compile-time string literal.
// ---------------------------------

template<auto& nameArray, typename FuncTuple>
struct Dispatcher {
    FuncTuple _funcs;
    
    using DispatcherTag = Tag<Dispatcher>;
    
    template<size_t nameIndex>
    static constexpr DispatcherTag TAG = {nameArray[nameIndex]};
    
    static constexpr DispatcherTag INVALID_TAG = {};

    Dispatcher(const FuncTuple& funcs) : _funcs(funcs) {
        singleton<Dispatcher> = this;
    }

    template<size_t nameIndex = 0>
    static constexpr auto& tag(string_view name) {
        if(name == nameArray[nameIndex]) {
            return TAG<nameIndex>;
        } else {
            if constexpr (nameIndex+1 < nameArray.size()) {
                return tag<nameIndex+1>(name);
            } else {
                return INVALID_TAG;
            }
        }
    }

    static constexpr size_t index(string_view name) {
        for(size_t nameIndex = 0; nameIndex < nameArray.size(); ++nameIndex) {
            if(name == nameArray[nameIndex]) {
                return nameIndex;
            }
        }
        return nameArray.size();
    }
    
    constexpr auto& operator()(const char* name) const {
        return tag(name);
    }

    template<auto& tag, typename... Args>
    auto call(Args... args) const {
        static constexpr size_t INDEX = index(tag.name);
        static constexpr bool VALID = INDEX != nameArray.size();
        static_assert(VALID, "Invalid tag.");

        return get<INDEX*VALID>(_funcs)(args...);
    }
};

template<auto& nameArray, typename FuncTuple>
auto makeDispatcher(const FuncTuple& funcs) {
    return Dispatcher<nameArray, FuncTuple>(funcs);
}

// ---------------------------------
// SHORTHAND: OPTION 1
// ---------------------------------
// Use a singleton pattern and a helper to let a tag be associated with a
// specific dispatcher, so that the call-site need not specify dispatcher twice
// ---------------------------------

template<auto& tag, typename... Args>
auto call(Args... args) {
    using Tag = remove_reference_t<decltype(tag)>;
    using ParentDispatcher = typename Tag::Parent;
    static auto dispatcher = singleton<ParentDispatcher>;

    return dispatcher->template call<tag>(args...);
}

// ---------------------------------
// SHORTHAND: OPTION 2
// ---------------------------------
// Use a string template user-defined literal operator to shorten call-site syntax
// gcc supports this as an extension implementing proposal N3599 (standardized in C++20)
// If warnings occur, try pragma GCC diagnostic ignored "-Wgnu-string-literal-operator-template"
// ---------------------------------

// Need characters to be in contiguous memory on the stack (not NTTPs) for TAG_FROM_LITERAL
template<char... name>
constexpr char NAME_FROM_LITERAL[] = {name..., '\0'};

// Don't need to specify Dispatcher with user-defined literal method; will use dispatcher.check<>()
struct TagFromLiteral {};

// Need to have a constexpr variable with linkage to use with dispatcher.check<>()
template<char... name>
constexpr Tag<TagFromLiteral> TAG_FROM_LITERAL = {NAME_FROM_LITERAL<name...>};

// Create a constexpr variable with linkage for use with dispatcher.check<>(), via "MyTag"_TAG
template<typename Char, Char... name>
constexpr auto& operator"" _TAG() {
    return TAG_FROM_LITERAL<name...>;
}

// ---------------------------------
// SHORTHAND: OPTION 3
// ---------------------------------
// Use a macro so the call-site need not specify dispatcher twice
// ---------------------------------

#define DISPATCH(dispatcher, name) dispatcher.call<dispatcher(name)>

// ---------------------------------
// COMMON: TEST FUNCTIONS
// ---------------------------------

bool testFunc1(int) { cout << "testFunc1" << endl; }
bool testFunc2(float) { cout << "testFunc2" << endl; }
bool testFunc3(double) { cout << "testFunc3" << endl; }

static constexpr auto funcs = make_tuple(&testFunc1, &testFunc2, &testFunc3);
static constexpr auto names = array{"one", "two", "three"};

int main()
{
    // Create a test dispatcher
    auto dispatcher = makeDispatcher<names>(funcs);

    // LONG-HAND: call syntax: a bit verbose, but operator() helps
    dispatcher.call<dispatcher("one")>(1);
    
    // SHORTHAND OPTION 1: non-member helper, singleton maps back to dispatcher
    call<dispatcher("one")>(1);

    // SHORTHAND OPTION 2: gcc extension for string UDL templates (C++20 standardizes this)
    dispatcher.call<"one"_TAG>(1);

    // SHORHAND OPTION 3: Macro
    DISPATCH(dispatcher, "one")(1);

    return 0;
}