当用作函数参数时,C ++“忘记”该变量为constexpr

时间:2018-10-15 07:59:14

标签: c++ c++17 constexpr

我有以下代码让我很恼火:编译器无法看到作为参数传递给函数的变量是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;
    }
}

如果不清楚我想要什么:我想更改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 {

5 个答案:

答案 0 :(得分:9)

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

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

constexpr auto i = 1;
constexpr auto j = foo(i);    

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

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

constexpr auto i = 1;
constexpr auto j = foo<i>();

一种可能的解决方案是使用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;
    }
}

由Constantinos Glynos建议的添加。

摘自 Scott Meyers Effective Modern C ++ 一书,第15条,第98页:

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

答案 1 :(得分:2)

另一个答案具有正确的解决方法,但我认为推理与参数无关,而是与此处的lambda捕获有关:

constexpr int cnt = [/* &arr, */&is_even]() 

实际上,我们可以使用以下代码测试各种情况:

#include <array> 
#include <iostream>

template <size_t N>
constexpr int foo(const std::array<int, N>& arr) {
    return [&arr] () { return arr.size(); }();
}

template <size_t N>
constexpr int bar(const std::array<int, N>& arr) {
    int res{};
    for (auto i : arr) {
        res++;
    }
    return res;
}

template <size_t N>
constexpr int baz(const std::array<int, N>& arr)     {
    constexpr int test = [&arr] () constexpr {
        return bar(arr);
    }();
    return test;
}

int main() {
    constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};
    constexpr std::array<int, foo(arr)> test{};
    constexpr std::array<int, bar(arr)> test2{};
    constexpr std::array<int, baz(arr)> test3{};
}   

请注意,初始化test3的行无法编译。但是,这样编译就可以了:

template <size_t N>
constexpr int baz(const std::array<int, N>& arr) {
    return bar(arr);
}

那么,这是什么问题?好吧,lambda实际上只是荣耀的函子,在内部它看起来像这样:

struct constexpr_functor {
    const std::array<int, 5>& arr;
    constexpr constexpr_functor(const std::array<int, 5>& test)
        : arr(test) { }
    constexpr int operator()() const {
        return bar(arr);
    }
};
// ...
constexpr constexpr_functor t{arr};
constexpr std::array<int, t()> test3{};

请注意,现在我们收到一条显示实际问题的错误消息:

test.cpp:36:33: note: reference to 'arr' is not a constant expression
test.cpp:33:34: note: declared here
    constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};

另一个答案引用了斯科茨·迈耶(Scotts Meyer)的书,但误解了这些引用。该书实际上显示了在constexpr情况下使用的参数的几个示例,但是引号只是说,如果传递非constexpr参数,则该函数可以在编译时运行。

答案 2 :(得分:2)

按照Evg的建议,因此将数字作为std::integer_sequence的模板参数传递,但将整数序列作为get_evens()函数的参数而不是模板参数传递,则可以使用数字直接在get_evens()内部。

我的意思是……您可以按如下方式简化get_evens()编辑:根据Evg的建议进一步简化(谢谢!)

template <typename T, T ... Ts>
constexpr auto get_evens (std::integer_sequence<T, Ts...> const &)
 {
   std::array<T, (std::size_t(!(Ts & T{1})) + ...)> result{};

   std::size_t idx = 0;

   ((void)(Ts & 1 || (result[idx++] = Ts, true)), ...);

   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;
 }

答案 3 :(得分: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;
    }
}

这个works in g++,但是在clang中,我们遇到了一个问题,因为begin on an array isn't properly constexpr with at least one library。也许g ++违反了标准,而clang却没有。

答案 4 :(得分:1)

template<auto t0, auto...ts>
struct ct_array:
  std::array<decltype(t0) const, 1+sizeof...(ts)>,
  std::integer_sequence<decltype(t0), t0, ts...>
{
  ct_array():std::array<decltype(t0) const, 1+sizeof...(ts)>{{t0, ts...}} {};
};

template<class target, auto X>
struct push;
template<auto X>
struct push<void, X>{using type=ct_array<X>;};
template<auto...elems, auto X>
struct push<ct_array<elems...>, X>{using type=ct_array<elems...,X>;};
template<class target, auto X>
using push_t= typename push<target, X>::type;

template<class target>
struct pop;
template<auto x>
struct pop<ct_array<x>>{using type=void;};
template<auto x0, auto...xs>
struct pop<ct_array<x0, xs...>>{using type=ct_array<xs...>;};
template<class target>
using pop_t=typename pop<target>::type;

template<class lhs, class rhs, class F, class=void>
struct transcribe;
template<class lhs, class rhs, class F>
using transcribe_t = typename transcribe<lhs, rhs, F>::type;

template<auto l0, auto...lhs, class rhs, class F>
struct transcribe<ct_array<l0, lhs...>, rhs, F,
  std::enable_if_t<F{}(l0) && sizeof...(lhs)>
>:
  transcribe<pop_t<ct_array<l0, lhs...>>, push_t<rhs, l0>, F>
{};
template<auto l0, auto...lhs, class rhs, class F>
struct transcribe<ct_array<l0, lhs...>, rhs, F,
  std::enable_if_t<!F{}(l0) && sizeof...(lhs)>
>:
  transcribe<pop_t<ct_array<l0, lhs...>>, rhs, F>
{};
template<auto lhs, class rhs, class F>
struct transcribe<ct_array<lhs>, rhs, F, void>
{
  using type=std::conditional_t< F{}(lhs), push_t<rhs, lhs>, rhs >;
};
template<class lhs, class F>
using filter_t = transcribe_t<lhs, void, F>;

// C++20
//auto is_even = [](auto i)->bool{ return !(i%2); };
struct is_even_t {
  template<class T>
  constexpr bool operator()(T i)const{ return !(i%2); }
};
constexpr is_even_t is_even{};

template<auto...is>
static constexpr auto get_evens(ct_array<is...>) {
  return filter_t< ct_array<is...>, decltype(is_even) >{};
}

Live example

测试代码:

auto arr = ct_array<11, 22, 33, 44, 55>{};
for (const int i : get_evens(arr)) {
    std::cout << i << " " << std::endl;
}