2 votes

pourquoi std::integral_constant est nécessaire dans l'explosion d'un std::tuple ?

#include <iostream>

#include <tuple>
#include <iostream>
#include <utility>

template <std::size_t... Idx>
auto make_index_dispatcher(std::index_sequence<Idx...>) 
{
    return [](auto&& f) { (f(std::integral_constant<std::size_t, Idx>{}), ...); };
}

template <std::size_t N>
auto make_index_dispatcher() 
{
    return make_index_dispatcher(std::make_index_sequence<N>{});
}

template <typename Tuple, typename Func>
void for_each(Tuple&& t, Func&& f) 
{
    constexpr auto n = std::tuple_size<std::decay_t<Tuple>>::value;
    auto dispatcher = make_index_dispatcher<n>();
    dispatcher([&f, &t](auto idx) { f(std::get<idx>(std::forward<Tuple>(t))); });
}

int main() 
{
    for_each(std::make_tuple(1, 42.1, "hi"), [](auto&& e) {std::cout << e << ","; });
}

Question 1> Pourquoi je dois utiliser std::integral_constant<std::size_t, Idx>{} au lieu de simplement Idx dans l'énoncé suivant ? D'après ce que j'ai compris, std::integral_constant<std::size_t, Idx> est un type. Est-il vrai que std::integral_constant<std::size_t, Idx>{} est une valeur de Idx ?

// OK

template <std::size_t... Idx>
auto make_index_dispatcher(std::index_sequence<Idx...>) 
{
    return [](auto&& f) { (f(std::integral_constant<std::size_t, Idx>{}), ...); };
}

// Erreur

template <std::size_t... Idx>
auto make_index_dispatcher(std::index_sequence<Idx...>) 
{
    return [](auto&& f) { (f(Idx), ...); };
}

Est-il vrai que std::get expression constante attendue en temps de compilation alors que Idx n'est PAS une expression constante en temps de compilation ?

Question 2> Pourquoi nous ne pouvons pas passer std::index_sequence par référence ?

// Erreur : auto make_index_dispatcher(std::index_sequence<Idx...>&)

Merci.

6voto

Vittorio Romeo Points 2559

Pourquoi dois-je utiliser std::integral_constant{} au lieu de simplement Idx dans l'instruction suivante ?

Parce que les arguments des fonctions ne sont jamais expressions constantes . Vous pouvez simplement passer les indices en tant que paramètres de modèle non typés, qui sont les suivants expressions constantes . Cela fonctionne particulièrement bien avec un modèle lambda C++20 :

template <std::size_t... Idx>
auto make_index_dispatcher(std::index_sequence<Idx...>) 
{
    return [](auto&& f) { (f.template operator()<Idx>(), ...); };
}

template <typename Tuple, typename Func>
void for_each(Tuple&& t, Func&& f) 
{
    constexpr auto n = std::tuple_size<std::decay_t<Tuple>>::value;
    auto dispatcher = make_index_dispatcher<n>();
    dispatcher([&f, &t]<auto Idx>(){ f(std::get<Idx>(std::forward<Tuple>(t))); });
}

exemple en direct sur godbolt.org


Pourquoi ne pouvons-nous pas passer std::index_sequence par référence ?

Vous pouvez le faire, mais vous devez invoquer votre fonction avec une lvalue comme avec n'importe quelle autre fonction qui n'est pas de l'ordre de l'écriture. const référence. Cela se compile :

template <std::size_t... Idx>
auto make_index_dispatcher(std::index_sequence<Idx...>&) 
{
}

int main()
{
    std::index_sequence<> is;        
    make_index_dispatcher(is);
}

Aussi, c'est complètement inutile.


De plus, votre code entier peut simplement être :

int main() 
{
    std::apply([](auto&&... xs)
    {
        ((std::cout << xs << ','), ...);
    }, std::make_tuple(1, 42.1, "hi"));
}

4voto

Holt Points 6689

Pourquoi dois-je utiliser std::integral_constant{} au lieu de simplement Idx dans l'instruction suivante ?

Est-il vrai que std::get s'attend à une expression constante de compilation alors que Idx n'est PAS une expression constante de compilation ?

std::get<> s'attend à une expression en temps de compilation, mais vous n'utilisez pas réellement l'option idx directement (ce qui n'est pas une expression en temps de compilation).

std::integral_constant<std::size_t, I> a une pratique constexpr l'opérateur de conversion en std::size_t qui renvoie I donc quand tu le fais :

std::get<idx>(...)

...vous êtes en train de faire std::get<(std::size_t)idx> y (std::size_t)idx est une expression en temps de compilation puisque l'opérateur de conversion est constexpr .

Si vous n'emballez pas Idx à l'intérieur d'un std::integral_constant le type de idx dans le lambda générique sera std::size_t et tout ce qui précède ne fonctionnera pas.

2voto

max66 Points 4276

Pourquoi dois-je utiliser std::integral_constant{} au lieu de simplement Idx dans l'instruction suivante ?

Juste pour le plaisir...

Ce que vous dites est vrai (pour autant que je le sache) jusqu'à C++17 (parce que les std::get() doit être connu au moment de la compilation et l'utilisation de l'option std::integral_constant est une manière élégante de passer une constante connue au moment de la compilation comme type d'une fonction auto dans un lambda générique (C++14 ou ultérieur) ; voir d'autres réponses pour des explications plus claires) mais ce n'est plus vrai (vous ne devez pas nécessairement utiliser std::integral_constant ) à partir de C++20.

En fait, C++20 introduit les lambdas template, de sorte que vous pouvez réécrire votre code comme suit, en passant la balise Idx comme paramètres de modèle et sans std::integral_constant

#include <tuple>
#include <iostream>
#include <utility>

template <std::size_t... Idx>
auto make_index_dispatcher(std::index_sequence<Idx...>) 
{
    return [](auto&& f) { (f.template operator()<Idx>(), ...); };
} // ......................^^^^^^^^^^^^^^^^^^^^^^^^^^^^   modified lambda call                        

template <std::size_t N>
auto make_index_dispatcher() 
{
    return make_index_dispatcher(std::make_index_sequence<N>{});
}

template <typename Tuple, typename Func>
void for_each(Tuple&& t, Func&& f) 
{
    constexpr auto n = std::tuple_size<std::decay_t<Tuple>>::value;
    auto dispatcher = make_index_dispatcher<n>();
    dispatcher([&f, &t]<std::size_t I>() { f(std::get<I>(std::forward<Tuple>(t))); });
} // ..................^^^^^^^^^^^^^^^^^ template parameter added, argument removed

int main() 
{
    for_each(std::make_tuple(1, 42.1, "hi"), [](auto&& e) {std::cout << e << ","; });
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X