Как написать функцию constexpr, которая работает с перенаправленным набором ссылок? - PullRequest
0 голосов
/ 10 февраля 2019

Я написал функцию constexpr, которая вычисляет сумму размеров элементов кортежа.

При прямом вызове функции функция компилирует как кортеж значений, так и кортеж ссылок.

При вызове через шаблонную функцию она все равно компилируется с кортежем значений, но не работает с кортежем ссылок.

Я могу обойти свою проблему, используя кортеж указателей вместо кортежа ссылок, ноAPI того, что я пишу (шаблонный набор функций для облегчения написания SPI и драйвера I²C для микроконтроллеров) будет менее чистым.

Спасибо за любую помощь.

Ps: я использую gcc8.2 с использованием стандарта c ++ 17.

Александр

#include <tuple>

template <typename> struct is_tuple_t: std::false_type {};
template <typename ...T> struct is_tuple_t<std::tuple<T...>> : std::true_type {};

template<typename Type>
constexpr bool is_tuple(const Type&) {
    if constexpr (is_tuple_t<Type>::value) 
        return true;
    else
        return false;
}

template<class F, class...Ts, std::size_t...Is>
constexpr void for_each_in_tuple(const std::tuple<Ts...> & tupl, F func,
             std::index_sequence<Is...>){
    using expander = int[];
    (void)expander { 0, ((void)func(std::get<Is>(tupl)), 0)... };
}

template<class F, class...Ts>
constexpr void for_each_in_tuple(const std::tuple<Ts...> & tupl, F func){
    for_each_in_tuple(tupl, func, std::make_index_sequence<sizeof...(Ts)>());
}

template <typename T>
constexpr size_t size_of_tuple(const T &tup) {
    static_assert(is_tuple(tup) == true, "error size_of_tuple argument must be a tuple");
    size_t s=0;
    for_each_in_tuple(tup, [&s](auto &&x) {
        s += sizeof(x);
    });
    return s;
}

template<typename Tb>
constexpr size_t foo(const Tb&& tup)
{
    constexpr size_t st = size_of_tuple(tup);
    return st;
}

int main()
{
    uint16_t var;

    constexpr size_t s1 = size_of_tuple(std::make_tuple(1)) ;          // OK
    constexpr size_t s2 = size_of_tuple(std::forward_as_tuple(var)) ;  // OK

    constexpr size_t f1 = foo(std::make_tuple(1)) ;          // OK
    constexpr size_t f2 = foo(std::forward_as_tuple(var)) ;  // FAIL
}

Ответы [ 3 ]

0 голосов
/ 12 февраля 2019

Итак, мы переписываем функцию, которая компилируется, по крайней мере, с помощью gcc 8.2:

template
<std::size_t position, class T>
struct SizeOfTupImpl{
static constexpr size_t
size() {
  if constexpr (position != 0) {
  return sizeof(std::tuple_element_t<position, T>) +
         SizeOfTupImpl<position - 1, T>::size();
  } else {
  return sizeof(std::tuple_element_t<0, T>);
  }
}
};

template<class T>
constexpr size_t
size_of_tuple(const T& tup) {
  static_assert(is_tuple(tup) == true, "error size_of_tuple argument must be a tuple");
  constexpr std::size_t dimension = std::tuple_size<T>::value;
  return SizeOfTupImpl<dimension - 1, T>::size();
}

Поскольку я новичок в абсолютном шаблонном метапрограммировании, не стесняйтесь указывать мне на более элегантное решение!

Александр

0 голосов
/ 12 февраля 2019

Проблемы вашего первоначального подхода должным образом объяснены в Ответ Окталиста .

Обратите внимание, что size_of_tuple может быть реализовано в одну строку с использованием C ++ 17 выражений сгиба :

template<class... Ts>
constexpr std::size_t size_of_tuple(const std::tuple<Ts...>&) {
  return (0 + ... + sizeof(Ts));
}

Однако эту функцию сложно использовать из-за аргумента типа const std::tuple<Ts...>&.Таким образом, может быть желательно ввести пустой тип тега, который может быть «передан» этим метафункциям.Эта идея объясняется в главе Boost.Hana о вычислениях типов .

Ниже приведен полный пример.

static_assert(__cplusplus >= 201703L, "C++17 required");

#include <cstddef>
#include <cstdint>

#include <tuple>

// the empty tag type
template<class T>
struct Type {};

////////////////////////////////////////////////////////////////////////////////

template<class... Ts>
constexpr std::size_t size_of_tuple(Type< std::tuple<Ts...> >) {
  return (0 + ... + sizeof(Ts));
}

static_assert(0 == size_of_tuple(Type< std::tuple<> >{}));
static_assert(12 == size_of_tuple(Type< std::tuple<int32_t, int64_t> >{}));
static_assert(12 == size_of_tuple(Type< std::tuple<int32_t&, int64_t&> >{}));
static_assert(2 == size_of_tuple(Type< std::tuple<char&, char> >{}));
static_assert(6 == size_of_tuple(Type< std::tuple<int32_t&, char&, char> >{}));

// fails to compile (for good reasons):
//static_assert(8 == size_of_tuple(Type< std::tuple<int32_t, uint64_t> >{}));

Кроме того, вы можете рассмотреть возможность использованияstd::integral_constant в качестве типа возврата.Это может пригодиться, если вы хотите передать размер в другую функцию и использовать его в качестве значения constexpr.Обратите внимание, что std::integral_constant s неявно конвертируются в их ::type.Неявное преобразование является одной из причин, по которой Boost.Hana вводит свой собственный тип "целочисленная константа".Некоторые интересные примеры можно найти в главе Boost.Hana о числах времени компиляции .В любом случае, вот простой пример с неявно конвертируемым std::integral_constant:

#include <cstddef>

#include <tuple>
#include <type_traits>

template<class... Ts>
constexpr auto better_size_of_tuple(Type< std::tuple<Ts...> >) {
  constexpr std::size_t ret = (0 + ... + sizeof(Ts));
  return std::integral_constant<std::size_t, ret>{};
}
0 голосов
/ 10 февраля 2019
template<typename Tb>
constexpr size_t foo(const Tb&& tup)
{
  constexpr size_t st = size_of_tuple(tup);
  return st;
}

В этой функции tup не является константным выражением, поэтому его нельзя использовать в инициализаторе переменной constexpr.

[expr.const] ¶2

Выражение e является основным константным выражением , если при оценке e [...] не будет выполнено одно из следующих выражений:

  • [...]
  • id-выражение , которое ссылается на переменную или член данных ссылочного типа, если ссылка не имеет предшествующей инициализации и либо
    • инициализируется с помощью постоянного выражения или
    • его время жизни началось в пределах оценки e

Любой из нихвместо этого должно работать:

template<typename Tb>
constexpr size_t foo(const Tb&& tup)
{
  size_t st = size_of_tuple(tup);
  return st;
}

template<typename Tb>
constexpr size_t foo(const Tb&& tup)
{
  return size_of_tuple(tup);
}

Обратите внимание, что Clang по-прежнему отклоняет ваш код за другие нарушения того же правила.


В конечном счете, ваши size_of_tuple и is_tupleоба несовершенны, так как их нельзя использовать в константных выражениях, если их аргумент является ссылкой.Если вы хотите использовать этот функциональный синтаксис, вам нужно что-то вроде type_c от Boost.Hana:

template <typename T>
class type {};
template <typename T>
constexpr type<T> type_c{};

template <typename T>
constexpr bool is_tuple(type<T>) {
    return is_tuple_t<T>::value;
}

template <typename T>
constexpr size_t size_of_tuple(type<T> tup) {
    static_assert(is_tuple(tup), "size_of_tuple argument must be a tuple");
    //...
}

template<typename Tb>
constexpr size_t foo(const Tb&& tup)
{
  size_t st = size_of_tuple(type_c<std::remove_cvref_t<decltype(tup)>>);
  return st;
}

uint16_t var = 42;
constexpr size_t f2 = foo(std::forward_as_tuple(var));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...