0

编辑:重命名,因为我的最终解决方案不使用中毒方法。

我正在寻找一种方法来防止在运行时调用 constexpr 方法。我正在编写一个接受字符串文字的函数,所以我不能简单地使用 NTTP 作为需要constexpr参数的方式:

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

因为这样即使是合法的 constexpr 使用也会变得很麻烦,需要值具有静态链接,并且您不能输入字符串文字。我想要做:

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

原因是因为我根据值列表检查字符串,并希望静态检查参数是否包含在允许的集合中。我可以很容易地做到这一点:只需throw()'ing 一个constexpr函数,就可能导致编译时错误。但我不希望生成带有一些导致程序在运行时退出的分支的生产代码的可能性。这将在我的领域造成一个重大问题;此功能在执行“其他重要事情”的程序中是一项不错的功能,如果程序终止,则会发生坏事。

我读到了一大堆可能的方法来做到这一点:

  1. 使用 C++20 consteval- 没有 C++20
  2. 使用 C++20 std::is_constant_evaluated- 没有 C++20
  3. 通过将结果返回给未定义的符号(例如extern int i,从未定义的符号)在运行时毒化方法。如果在编译时调用该方法,编译器永远不会创建返回该符号的代码,但如果在运行时调用该方法,它会创建返回该符号的代码,从而导致链接错误。工作,但丑陋的链接器错误;不是我最喜欢的。我的帖子中显示了一个版本:Constexpr functions not called at compile-time if result is ignored
  4. 在 C++17 中,noexcept自动添加到对constexpr在上下文中实际调用的函数的任何调用constexpr中。所以你可以做noexcept(foo(1))vs noexcept(foo(i))for constexpr int foo(int i)(未明确声明noexcept) 来检测是否i导致调用为constexpr/not。但是您不能在constexpr接受了某些参数的函数中执行此操作 - 您需要从调用站点执行此操作。所以:可能需要一个辅助宏(不是我最喜欢的,但它有效)。
  5. 如果 lambda 范围之外的某些变量不是,则创建一个返回类型无效的 lambda constexpr。这篇文章详细介绍:https ://stackoverflow.com/a/40413051

所以我倾向于使用#3 或#4 + 宏,但是***这篇文章是关于#5 ***,或者是全新的想法。

例如,任何人都可以想出一种没有 lambda 的方法来执行 #5 吗?在那之后,我想看看我是否可以想出一种在constexpr函数本身中使用它的方法,而不是要求它从调用站点使用。现在,如果在运行时调用函数,只需尝试毒化constexpr它,忘记“检测”函数调用是否为constexpr.

我可以像作者一样通过在其中创建一个 lambda 来重新创建 #5 的结果main,但这实际上并不是很有用,而且我仍然不相信它是完全合法的。首先,任何可以用 lambda 完成的事情都可以在没有 lambda 的情况下完成——对吧???如果没有 lambda,我什至无法让原作者的方法工作。这似乎是让它在main().

以下是我尝试在没有 lambda 的情况下重新创建 #5 的几个想法。具有十亿多个排列的实时示例,但都不起作用: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;
}

还有一件事:我考虑制作一个预定义的允许标签列表(将字符串包装在一个结构中,这样它就可以是一个 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")>();
}
4

1 回答 1

0

这是我自己的答案,它检查字符串文字是否在 COMPILE-TIME 的允许集中,然后根据该字符串的值执行操作。不需要函数中毒constexpr,并且仍然没有繁琐的要求来提供具有静态链接的字符串文字。

归功于 Jarod42 的“速记选项 2”,它使用了gcc字符串模板用户定义文字的扩展,它是 C++20 的一部分,但不是 C++17 的一部分。

我认为我对三种“速记”调用站点语法中的任何一种都感到满意。我仍然欢迎任何替代方案或改进,或对我搞砸的地方的指点。完美转发等留给读者练习;-)

现场演示: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;
}
于 2020-08-24T20:32:16.177 回答