【问题标题】:Force expression to constexpr强制表达式为 constexpr
【发布时间】:2019-06-23 11:08:55
【问题描述】:

给定两个constexpr 函数,是否可以将它们组合成一个函数?

template <char... C>
constexpr int boo()
{
    char ch[] = { C... };
    int count = 0;

    for (char c : ch)
    {
        if (c != '0') count += 1;
    }

    return count;
}

template <char... C>
constexpr auto foo()
{
    std::array<char, boo<C...>()> x{};

    return x;
}

如示例所示,我可以将“count”作为常量返回。 我的问题是我不能在声明的函数中使用 'count' 作为常量。也就是说,如果“boo()”的主体放在“foo()”中,编译器会抛出“count”不是常量。

【问题讨论】:

  • 为什么要在函数中为constexpr?它不可能是一成不变的,它会变化。
  • 然而 boo 可以将其作为常量返回。我只是认为有一种方法可以在不需要两个 constexpr 函数的情况下做到这一点。
  • 这就是语言的工作原理。函数参数和非constexpr局部变量不能是编译时常量。
  • 您的代码在 C++14 模式下运行良好。难道你还在使用 C++11?
  • 如果我们能真正看到您省略的代码会很有帮助。

标签: c++ c++17 variadic-templates template-meta-programming constexpr


【解决方案1】:

问题是std::array 需要一个常量作为大小值。

如果您定义count 并在foo() 内部对其进行修改,则count(如foo() 函数内部所示)是一个变量,而不是一个常量。

所以你需要在另一个地方修改它:在一个constexpr 函数中,所以返回的值变成一个编译时已知的常量。

如果你可以使用 C++17,那么模板折叠(从 Evg 和 Rakete1111 改进;谢谢),你可以完全避免 bar()

template <char... C>
constexpr auto foo()
{
    std::array<char, (0u + ... + (C != '0'))> x{};

    return x;
}

但如果你只有 C++11,你需要递归

template <typename = void>
constexpr std::size_t bar ()
 { return 0u; }

template <char C0, char ... C>
constexpr std::size_t bar ()
 { return bar<C...>() + (C0 == '0' ? 0u : 1u); }

template <char... C>
constexpr std::array<char, bar<C...>()> foo()
 { return {}; }

【讨论】:

  • 为了通用性,我会使用0 + ... + 。参数包可以为空,std::array 的大小可以为零。
  • 您也可以使用C != '0' 代替条件运算符
  • 不过,C++17 之前的解决方案仍然使用另一个函数模板。
  • @Evg - 好点;我习惯于使用 &amp;&amp;|| 进行模板折叠,但你说得对:总和很重要。
  • 虽然我知道这一点,但我认为会有一种方法可以合并两个函数体。
【解决方案2】:

对于 C++14 及更高版本,如果您的目标是“合并”主体,您可以简单地在函数模板中定义一个类型:

template <char... C>
constexpr auto foo()
{
    struct {
        constexpr int operator()() {
            char ch[] = { C... };
            int count = 0;

            for (char c : ch)
            {
                if (c != '0') count += 1;
            }

            return count;
        };
    } boo;

    std::array<char, boo()> x{};

    return x;
}

如果你有 C++17,你也可以在常量表达式中使用 lambda,所以你可以将 boo 缩短为:

constexpr auto boo = []() { /* ... */ };

在 C++20 中,您将能够将 lambda 表达式直接编写为模板参数,因此您可以进一步简化为(如果您真的想要的话):

std::array<char, []() { /* ... */ }()> x{};

话虽如此,一般来说,我想说的是,在标头中包含模板使用的各种额外代码但不属于公共接口的通常(更简洁)方法是将它们放在 @987654325 @ 或类似名称的命名空间:

namespace detail {
    template <char... C>
    constexpr int boo()
    {
        /* ... */
    }
}

template <char... C>
constexpr auto foo()
{
    /* ... detail::boo<C...>() ... */
}

【讨论】:

  • @Evg 添加了它,还提到了 C++20 中的另一种方法以进一步缩短它。
猜你喜欢
  • 2020-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-16
  • 1970-01-01
  • 1970-01-01
  • 2012-12-27
  • 1970-01-01
相关资源
最近更新 更多