当用作函数参数时,C++"遗忘"该变量是constexpr

NoS*_*tAl 6 c++ constexpr c++17

我有以下代码,我厌倦了编译器无法看到作为函数的参数传递的变量是constexpr的事实,所以我必须使用arity 0函数而不是1参数函数.

我知道这不是编译器错误,但我想知道是否有成语可以解决这个问题.

#include <array>
#include <iostream>

static constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};

template <typename C, typename P, typename Y>
static constexpr void copy_if(const C& rng, P p, Y yi3ld) {
    for (const auto& elem: rng) {
        if (p(elem)){
            yi3ld(elem);
        }
    }
}

// template<std::size_t N>
static constexpr auto get_evens(/* const std::array<int, N>& arr */) {
    constexpr auto is_even = [](const int i) constexpr {return i % 2 == 0;};
    constexpr int cnt = [/* &arr, */&is_even]() constexpr {
        int cnt = 0;
        auto increment = [&cnt] (const auto&){cnt++;};
        copy_if(arr, is_even, increment);
        return cnt;
    }();
    std::array<int, cnt> result{};
    int idx = 0;
    copy_if(arr, is_even, [&result, &idx](const auto& val){ result[idx++] = val;});
    return result;
}

int main() {
    // constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};
    for (const int i:get_evens(/* arr */)) {
        std::cout << i << " " << std::endl;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我不想要的是什么:我想更改get_evens签名,以便它在数组大小N上模板化,并且它需要1个类型的参数const std::array<int, N>&.

当我更改arr为函数参数时的错误消息没有帮助:

prog.cc:25:21:注意:'cnt'的初始化程序不是常量表达式prog.cc:19:19:注意:这里声明 constexpr int cnt = [&arr, &is_even]()constexpr {

Evg*_*Evg 9

函数参数永远不是常量表达式,即使在constexpr上下文中使用了函数:

constexpr int foo(int i)
{
    // i is not a constexpr
    return i + 1;
}

constexpr auto i = 1;
constexpr auto j = foo(i);    
Run Code Online (Sandbox Code Playgroud)

要模仿constexpr参数,请使用模板参数:

template<int i>
constexpr int foo()
{
    // i is constexpr
    return i + 1;
}

constexpr auto i = 1;
constexpr auto j = foo<i>();
Run Code Online (Sandbox Code Playgroud)

一种可能的解决方案是使用std::integer_sequence将整数编码为一个类型:

#include <array>
#include <iostream>
#include <type_traits>

template<typename P, typename Y, int... elements>
constexpr void copy_if_impl(P p, Y yi3ld, std::integer_sequence<int, elements...>) {
    ((p(elements) && (yi3ld(elements), true)), ...);
}

template<typename arr_t, typename P, typename Y>
constexpr void copy_if(P p, Y yi3ld) {
    copy_if_impl(p, yi3ld, arr_t{});
}

template<typename arr_t>
constexpr auto get_evens(){
    constexpr auto is_even = [](const int i) constexpr { return i % 2 == 0; };
    constexpr int cnt = [&is_even]() constexpr {
        int cnt = 0;
        auto increment = [&cnt](const auto&) { cnt++; };
        copy_if<arr_t>(is_even, increment);
        return cnt;
    }();

    std::array<int, cnt> result{};
    int idx = 0;
    copy_if<arr_t>(is_even, [&result, &idx](const auto& val) {
        result[idx++] = val; });
    return result;
}

int main()
{
    using arr = std::integer_sequence<int, 11, 22, 33, 44, 55>;
    for (const int i : get_evens<arr>()) {
        std::cout << i << " " << std::endl;
    }
}
Run Code Online (Sandbox Code Playgroud)

Constantinos Glynos建议增加.

来自Scott Meyers的Effective Modern C++一书,第15项,第98页:

  • constexpr函数可以在需要编译时常量的上下文中使用.如果constexpr在编译期间已知传递给此类上下文中的函数的参数值,则将在编译期间计算结果.如果在编译期间未知任何参数的值,则您的代码将被拒绝.
  • 当使用constexpr编译期间未知的一个或多个值调用函数时,它的作用类似于普通函数,在运行时计算其结果.这意味着您不需要两个函数来执行相同的操作,一个用于编译时常量,另一个用于所有其他值.该constexpr功能可以做到这一切.

  • @NoSenseEtAl很多人都被误导了'constexpr`功能.如果函数`F`是`constexpr`,如果`x`已经是常量表达式,则可以在编译时计算`F(x)`,但如果`x`是'x',则将在运行时计算`F(x)`仅在运行时可用,因此在`F`中,您不能将`x`视为常量表达式.只有'F(x)`的结果可以被认为是一个常量表达式. (2认同)

Yak*_*ont 1

#include <array>
#include <iostream>

static constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};

template <typename C, typename P, typename T>
static constexpr void invoke_if(const C& rng, P p, T target) {
    for (const auto& elem: rng) {
        if (p(elem)){
            target(elem);
        }
    }
}

constexpr bool is_even(int i) {
    return i % 2 == 0;
}

template<std::size_t N>
constexpr std::size_t count_evens(const std::array<int, N>& arr)
{
    std::size_t cnt = 0;
    invoke_if(arr, is_even, [&cnt](auto&&){++cnt;});
    return cnt;
}

template<std::size_t cnt, std::size_t N>
static constexpr auto get_evens(const std::array<int, N>& arr) {
    std::array<int, cnt> result{};
    int idx = 0;
    invoke_if(arr, is_even, [&result, &idx](const auto& val){ result[idx++] = val;});
    return result;
}

int main() {
    // constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};
    for (const int i:get_evens<count_evens(arr)>(arr)) {
        std::cout << i << " " << std::endl;
    }
}
Run Code Online (Sandbox Code Playgroud)

在 g++ 中有效,但在 clang 中我们遇到了一个问题,因为beginon an与至少一个库array不正确constexpr。或者也许 g++ 违反了标准,而 clang 没有。