constexpr字符串文字检查:语法简短,没有运行时的可能性 [英] constexpr string-literal checking: short syntax, no runtime possibility

查看:96
本文介绍了constexpr字符串文字检查:语法简短,没有运行时的可能性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm looking for a way to prevent a constexpr method from being called at runtime. I'm writing a function that accepts a string literal, so I cannot simply use a NTTP as a way to require a constexpr parameter:

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

因为即使合法的constexpr使用也变得麻烦,要求值具有静态链接,因此您不能输入字符串文字。我想做的事:

Because then even legitimate constexpr uses become cumbersome, requiring values to have static linkage, and you can't feed in a string literal. I want to do:

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

原因是因为我对照值列表检查了字符串,并希望静态检查参数是否包含在允许的集合中。我可以轻松做到这一点:只需在 constexpr 函数中使用 throw()即可,时间错误。但是我不希望通过某些分支来生成生产代码,从而导致程序在运行时退出。那会在我的领域造成一个重大问题。在执行其他重要内容的程序中,此功能是一个不错的选择。

The reason is because I check the string against a list of values, and want to STATICALLY check that the parameter is contained in the allowed set. I can do this easily: by just throw()'ing in a constexpr function, you can cause a compile-time error. But I do not want the possibility of generating production code with some branch that causes the program to exit at runtime. That would cause a major problem in my field; this feature is a nice-to-have in a program that does "other important stuff" and bad things happen if the program terminates.

我读到很多方法可以做到这一点:

I read about a whole bunch of possible ways to do this:


  1. 使用C ++ 20 consteval -没有C ++ 20

  2. 使用C ++ 20 std :: is_constant_evaluated -没有C ++ 20

  3. 在运行时通过将结果返回未定义符号(例如 extern int i 从未定义)。如果在编译时调用该方法,则编译器永远不会创建返回该符号的代码,但是如果在运行时调用该方法,则编译器不会创建该代码,从而导致链接错误。可行,但链接程序错误;不是我的最爱。我的文章中显示了该版本的版本:如果忽略结果,则在编译时不会调用Constexpr函数。。 li>
  4. 在C ++ 17中, noexcept 自动添加到对 constexpr 实际上在 constexpr 上下文中调用的函数。因此您可以将 noexcept(foo(1)) noexcept(foo(i)) c> constexpr int foo(int i)(未明确声明 noexcept )来检测是否 i 导致调用为 constexpr / not。但是您不能从已经接受了某些参数的 constexpr 函数中执行此操作-您需要在调用站点进行操作。因此:可能需要一个辅助宏(不是我的最爱,但它可以工作)。

  5. 如果lambda范围之外的某些变量不是<$ c $,则创建其返回类型无效的lambda。 c> constexpr 。这篇文章将详细介绍: https://stackoverflow.com/a/40413051

  1. Use C++20 consteval - don't have C++20
  2. Use C++20 std::is_constant_evaluated - don't have C++20
  3. Poison the method at runtime by returning a result to an undefined symbol (e.g. extern int i that never gets defined). The compiler never creates code which returns that symbol if the method is called at compile-time, but it does if the method is called at runtime, resulting in a link error. Works, but ugly linker error; not my favorite. A version of that is shown in my post here: Constexpr functions not called at compile-time if result is ignored.
  4. In C++17, noexcept gets automatically added onto any call to a constexpr function that actually gets called in a constexpr context. So you can do noexcept(foo(1)) vs noexcept(foo(i)) for constexpr int foo(int i) (not explicitly declared noexcept) to detect whether i causes the call to be constexpr/not. But you can't do that from within a constexpr function that has accepted some parameter - you need to do it from the call-site. So: probably requires a helper MACRO (not my favorite but it works).
  5. Create a lambda whose return type is invalid if certain variables outside the scope of the lambda are not constexpr. This post goes into detail: https://stackoverflow.com/a/40413051

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

So I'm leaning towards using #3 or #4 + a macro, but ***this post is about #5 ***, or totally new ideas.

例如,有人可以提出没有lambda的方法来做#5吗?之后,我想看看是否可以找到一种在 constexpr 函数本身中使用它的方法,而不是要求在调用站点使用它。现在,如果尝试在运行时调用 constexpr 函数,只需对其下毒,而不必检测到 $。函数调用是否为 constexpr

Can anyone come up with a way to do #5 without a lambda, for example? After that, I want to see if I can come up with a way to use it within the constexpr function itself rather than requiring it to be used from the call-site. For now, just try to poison a constexpr function if it is called at runtime, forget about "detecting" whether the function call is constexpr.

我可以通过在<$ c $中创建一个lambda来重新创建#5的结果。 c> main 就像作者所做的那样,但这实际上不是很有用,而且我仍然不相信这是完全合法的。首先,可以使用lambda进行的所有操作都可以在不使用lambda的情况下进行-对吗?没有lambda,我什至无法获得原始作者的工作方式。这似乎是使它在 main()之外工作的第一步。

I can re-create the results of #5 by creating a lambda in main as the author did, but that's not actually very useful, and I'm still not convinced that it's fully legal. To start with, anything that can be done with a lambda can be done without a lambda -- right??? I can't even get the original author's method to work without a lambda. That seems like a first required step to get it to work outside of main().

以下是我的一些建议试图在没有lambda的情况下重新创建#5。包含十亿个排列的现场示例,这些排列都不起作用: https://onlinegdb.com/B1oRjpTGP

Below are a couple ideas I've tried to recreate #5 without a lambda. Live example with a billion more permutations, none of which work: 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这样的语法会很好, code>,我的程序需要能够知道完整的标签集,因此它们必须位于数组或模板变量中,(3)使用数组不起作用,因为获取数组元素会产生 rvalue ,它没有链接,因此不能作为NTTP进行馈送,(4)使用带有显式专门化的模板变量来定义每个标签,需要模板变量是global-scope,对我来说效果不佳...

One more thing: I toyed with the idea of making a pre-defined list of allowed tags (wrap the string in a struct so it can be a NTTP), and putting them in some kind of container, and then having a method to retrieve them. The problems are: (1) the call-site syntax gets quite verbose to use them, (2) although it'd be fine for the call-site to use a syntax like MyTags::TAG_ONE, my program needs to be able to know the full set of tags, so they need to be in an array or a template variable, (3) using an array doesn't work, because getting an array element produces an rvalue, which doesn't have linkage, so can't be fed as a NTTP, (4) using a template variable with explicit specialization to define each tag requires the template variable to be global-scope, which doesn't work well for me...

这是我所能做的最好的事情-我认为这很丑...:

This is about the best I can do - I think it's kind of ugly...:

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")>();
}


推荐答案

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

Here's my own answer, which checks that a string literal is within an allowed set at COMPILE-TIME, then performs an action based upon the value of that string. No poisoning of constexpr functions is needed, and there are still no cumbersome requirements to provide string literals with static linkage.

Credit Jarod42用于简写选项2,它对字符串模板用户定义的文字使用了 gcc 扩展名,它是C ++ 20的一部分,而不是C ++ 17的一部分。

Credit goes to Jarod42 for "shorthand option 2", which uses a gcc extension for string template user-defined literals, which is part of C++20 but not C++17.

我对三个速记中的任何一个都感到很满意呼叫站点语法。我仍然欢迎任何替代方案或改进,或对我搞砸的事情提出建议。完美的转发等留给读者练习;-)

I think I'm happy enough with any of the three "shorthand" call-site syntaxes. I would still welcome any alternatives or improvements, or pointers on what I messed up. Perfect forwarding, etc. is left as an exercise for the reader ;-)

实时演示: 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;
}

这篇关于constexpr字符串文字检查:语法简短,没有运行时的可能性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆