C ++ «забывает», что переменная является constexpr при использовании в качестве аргумента функции - PullRequest
0 голосов
/ 15 октября 2018

У меня есть следующий код, где меня раздражает тот факт, что компилятор не может увидеть, что переменная, переданная в качестве аргумента функции, является 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 голосов
/ 15 октября 2018
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) >{};
}

Живой пример .

Тестовый код:

auto arr = ct_array<11, 22, 33, 44, 55>{};
for (const int i : get_evens(arr)) {
    std::cout << i << " " << std::endl;
}
0 голосов
/ 15 октября 2018
#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;
    }
}

это работает в g ​​++ , но в clang возникает проблема, потому что begin в array неправильно constexpr хотя бы с одной библиотекой .Или, возможно, g ++ нарушает стандарт, а clang - нет.

0 голосов
/ 15 октября 2018

Другой ответ имеет правильное решение, но я думаю, что рассуждения не имеют ничего общего с параметрами, а вместо этого связаны с лямбда-захватом здесь:

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

Действительно, мы можем протестировать различные сценарии с помощью этогоcode:

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

Итак, в чем здесь проблема?Ну, лямбды на самом деле просто прославляемые функторы, и внутренне это будет выглядеть примерно так:

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

другой ответ цитирует книгу Скотта Мейера, но неверно истолковывает цитаты.Книга фактически показывает несколько примеров параметров, используемых в ситуациях constexpr, но в кавычках просто говорится, что если вы передадите параметр non-constexpr, функция может работать во время компиляции.

0 голосов
/ 15 октября 2018

Следуя совету 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;
 }
0 голосов
/ 15 октября 2018

Аргумент функции никогда не является константным выражением, даже если функция используется в контексте 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;
    }
}

Дополнение, предложенное Константиносом Глиносом.

С Effective Modern C ++ книга Скотта Мейерса , пункт 15, стр.98:

  • constexpr Функции могут использоваться в контекстах, требующих компиляциипостоянные времениЕсли значения аргументов, которые вы передаете функции constexpr в таком контексте, известны во время компиляции, результат будет вычислен во время компиляции.Если какое-либо из значений аргументов неизвестно во время компиляции, ваш код будет отклонен.
  • Когда функция constexpr вызывается с одним или несколькими значениями, которые неизвестны во время компиляции, она действует как обычная функция, вычисляя свой результат во время выполнения.Это означает, что вам не нужны две функции для выполнения одной и той же операции: одна для констант времени компиляции и одна для всех других значений.Функция constexpr делает все это.
...