Using Std::Visit with Variadic Template Struct

Using std::visit with variadic template struct

Can someone please explain how this overloaded struct works? Especially what I didn't understand is the following declaration.

template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

That's an user-defined deduction guide (link to the working draft).

It's a feature of the language introduced by the latest revision of the standard along with class template arguments deduction. See also here for more details and a more user-friendly explanation.

This is not a proper explanation, but for the sake of simplicity you can consider it as an hint you can give to lead the deduction of the template arguments out of a set of parameters given to the constructor.


As a side note, here I found an example that is pretty clear and it's worth copying it over:

template<typename T>
struct Thingy { T t; };

Thingy(const char *) -> Thingy<std::string>;

// ...

Thingy thing{"A String"}; // thing.t is a `std::string`.

Credits are for @NicolBolas, an active user here on SO. Unfortunately I can't find the answer from which this example has been taken.

How to use variadic templates to proceed std::variant with std::visit?

This popular little "overloaded" class is always useful for passing lambdas to std::visit:

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

Then, instead of using a generic lambda and trying to deduce the right object type from the parameter type, just generate an overload for each Args.

template<typename ...Args>
std::shared_ptr<IFace> factory(const std::variant<typename Args::Key...>& v)
{
return std::visit(overloaded {
[](const typename Args::Key&) -> std::shared_ptr<IFace> { return std::make_shared<Args>(); }...
}, v);
}

Demo: https://godbolt.org/z/nKGf3c

Multiple std::variant visit with variadic templated helper

Following call would work:

int main() {
Possible<int> a = 16;
Possible<bool> b = true;

std::function<void(int, bool)> fun = [](int x, bool y) -> void {
std::cout << "All types set!" << std::endl;
};

ifAll(fun,
std::move(a),
std::move(b));
}

or switch your function signature to:

template <typename... Types>
void ifAll(std::function<void(Types...)> const& all, Possible<Types>&... possibles)

and then you can call it without std::move:

int main() {
Possible<int> a = 16;
Possible<bool> b = true;

std::function<void(int, bool)> fun = [](int x, bool y) -> void {
std::cout << "All types set!" << std::endl;
};

ifAll(fun, a, b);
}

Variadic template data structures

I dedicate this answer to @super that provides the link and the technical name for research. Thanks !

1 and 3. Let's suppose the template primary is:

template <size_t ...Rest>
struct Test {
int value = 1;
};

This means that the others (partial specialization) can be deducted, and with this different methods and values within the Struct / Class can be defined, example:

template <size_t A>
struct Test <A, A*2> {
int value = 2;
}

Any "Test" structure that was initialized with 2 parameters, and the second is the double of first (A*2) have value = 2. Example:

Test<5, 10> -> value = 2
Test <3, 6> -> value = 2;

Anything other than: "<A, A * 2>" will be assigned to the primary template

Test <1, 2, 3, 4> -> value = 1;
Test <4, 5> -> value = 1;
Test <3, 6, 8> -> value = 1;

That said, replying to the first topic:

template <class ...Rest>
struct Test {
int value = 1;
};

template <class T, ...Rest>
struct Test <T, Rest...> {
int value = 2;
};

The condition of the secondary template must satisfy the primary, which in this case is: "template <class ... Rest> struct Test {}", then any secondary template: template <class T, ... Rest> struct Test <T , Rest ...> will always be true, because Rest ... means 0 or more, and "T" is the first argument

Example below:

Test<int, bool> -> value = 2
Test <bool, float> -> value = 2
Test <bool> -> value = 2
Test <std::string> -> value = 2

But you can also create other secondary templates:

template <class ...Rest>
struct Test {
int value = 1;
};

template <>
struct Test <int, float> {
int value = 2;
};

template <class ...Rest>
struct Test <int, float, bool, Rest...> {
int value = 3;
};

In this example above, you are saying:

If the type is "<int, float>" the value will be 2
[Exact two arguments, int and float]

If the type is <int, float, bool, Rest ...> the value will be 3
[The first three arguments are int and float and bool, the rest if any]

Example:

Test <int, float> -> value = 2

Test <int, float, bool> -> value = 3
Test <int, float, bool, std::string> -> value = 3
Test <int, floot, bool, std::string, char> -> value = 3

Any other that does not satisfy any of the secondary templates will be assigned to the primary

Test <bool, float> -> value = 1
Test <int> -> value = 1
Test <int, float, char> -> value = 1

2.
Let's assume the following code

...
template <class T, class ...Rest>
void Print(T data, Rest ...others){
std::cout << data.first << std::endl;
}

int main(){
Test<int, float, bool> ex(15, 41.59, true);

Print(ex);
return 0;
}

Print waits as a parameter (T data, Rest ... others), that is: "data" is of type T. Then T data will be "ex"

If we change the print func to Print(Test <T, Rest ...> & data), C ++ will understand that the template to be passed must be a structure or class that satisfies the condition to be true, example: Print < int, float, bool > (ex).

"< int, float, bool >" was obtained automatically by c / c ++ of the variable "ex"

See the example below:

...
template <class A, class B, class C>
void Print(Test<A, B, C> &data, A za, B zq){
std::cout << data.first << std::endl;
std::cout << za << std::endl; //float
std::cout << zq << std::endl; //bool
}

int main(){
Test<int, float, bool> ex(15, 41.59, true);

Print(ex, 50);
return 0;
}

Again "< int, float, bool >" was obtained automatically by c / c ++ of the variable "ex" when Print(ex)

A is "int"

B is "float'

C is "bool"

The second parameter (A za) of the function "Print", must the same type (int) of the parameter that was deducted by "A" (int), the same for the third parameter (B zq)

3. Let's assume the following code:

template <class T>
struct Helper {
int value = 1;
};

template <>
struct Helper <int> {
int value = 2;
};

template <class A, class B, class C>
struct Helper<Test<A, B, C>> {
int value = 3;

void PrintA(A value){}

void PrintB(B value){}

void PrintAll(A aval, B bval, C cval){}
};

Any "Helper" structure that was initialized with a single < int > parameter will have value = 2

Any "Helper" structure that was initialized with a single parameter different from < int > and Test<A, B, C> will have value = 1;

Example:

Helper<int> -> value = 2

Helper<Test<int,bool,float>> -> value = 3
Helper<Test<int,float,char>> -> value = 3
Helper<Test<char,std::string,double>> -> value = 3

Anything other than: "<int>" and "<Test<A,B,C>>" will be assigned to the primary template

Helper <bool> -> value = 1
Helper <float> -> value = 1
Helper <char> -> value = 1

Note that the secondary templates must satisfy the primary, ie: template < class T > struct Helper {}

Therefore, the secondary templates after the definition of "Helper" must contain only a single "T", example < int >, or < float >, or <Test <A, Rest ... >>


template <class A, class B, class C>
struct Helper <Test <A, B, C >>

This means: The template to be passed to Helper must be a type of struct / class that satisfies the expression, so:

Helper <Test <int, float, bool >> satisfies the condition, but

Helper <Test <int, float >> does not satisfy, because "A" was deducted for int, "B" for float, and "C" was empty

Confusing templates in C++17 example of std::visit

What are the two lines declaring overloaded, just above int main(), mean?

The first one

template<class... Ts>
struct overloaded : Ts...
{ using Ts::operator()...; };

is a classic class/struct declaration/definition/implementation. Valid from C++11 (because use variadic templates).

In this case, overloaded inherits from all template parameters and enables (using row) all inherited operator(). This is an example of Variadic CRTP.

Unfortunately the variadic using is available only starting from C++17.

The second one

template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

is a "deduction guide" (see this page for more details) and it's a new C++17 feature.

In your case, the deduction guide says that when you write something as

auto ov = overloaded{ arg1, arg2, arg3, arg4 };

or also

overloaded ov{ arg1, args, arg3, arg4 };

ov becomes an overloaded<decltype(arg1), decltype(arg2), decltype(arg3), decltype(arg4)>

This permits you to write something as

overloaded
{
[](auto arg) { std::cout << arg << ' '; },
[](double arg) { std::cout << std::fixed << arg << ' '; },
[](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
}

that in C++14 was

auto l1 = [](auto arg) { std::cout << arg << ' '; };
auto l2 = [](double arg) { std::cout << std::fixed << arg << ' '; };
auto l3 = [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; }

overloaded<decltype(l1), decltype(l2), decltype(l3)> ov{l1, l2, l3};

-- EDIT --

As pointed by Nemo (thanks!) in the example code in your question there is another interesting new C++17 feature: the aggregate initialization of base classes.

I mean... when you write

overloaded
{
[](auto arg) { std::cout << arg << ' '; },
[](double arg) { std::cout << std::fixed << arg << ' '; },
[](const std::string& arg) { std::cout << std::quoted(arg) << ' '; }
}

you're passing three lambda functions to initialize three base classes of overloaded.

Before C++17, you could do this only if you wrote an explicit constructor to do it. Starting from C++17, it works automatically.

At this point, it seems to me that it can be useful to show a simplified full example of your overloaded in C++17 and a corresponding C++14 example.

I propose the following C++17 program

#include <iostream>

template <typename ... Ts>
struct overloaded : public Ts ...
{ using Ts::operator()...; };

template <typename ... Ts> overloaded(Ts...) -> overloaded<Ts...>;

int main ()
{
overloaded ov
{
[](auto arg) { std::cout << "generic: " << arg << std::endl; },
[](double arg) { std::cout << "double: " << arg << std::endl; },
[](long arg) { std::cout << "long: " << arg << std::endl; }
};
ov(2.1);
ov(3l);
ov("foo");
}

and the best C++14 alternative (following also the bolov's suggestion of a "make" function and his recursive overloaded example) that I can imagine.

#include <iostream>

template <typename ...>
struct overloaded;

template <typename T0>
struct overloaded<T0> : public T0
{
template <typename U0>
overloaded (U0 && u0) : T0 { std::forward<U0>(u0) }
{ }
};

template <typename T0, typename ... Ts>
struct overloaded<T0, Ts...> : public T0, public overloaded<Ts ...>
{
using T0::operator();
using overloaded<Ts...>::operator();

template <typename U0, typename ... Us>
overloaded (U0 && u0, Us && ... us)
: T0{std::forward<U0>(u0)}, overloaded<Ts...> { std::forward<Us>(us)... }
{ }
};

template <typename ... Ts>
auto makeOverloaded (Ts && ... ts)
{
return overloaded<Ts...>{std::forward<Ts>(ts)...};
}

int main ()
{
auto ov
{
makeOverloaded
(
[](auto arg) { std::cout << "generic: " << arg << std::endl; },
[](double arg) { std::cout << "double: " << arg << std::endl; },
[](long arg) { std::cout << "long: " << arg << std::endl; }
)
};
ov(2.1);
ov(3l);
ov("foo");
}

I suppose that it's matter of opinion, but it seems to me that the C++17 version is a lot simpler and more elegant.

Data structure with variadic templates

One solution is to create some partial specializations of Menu to unwrap the variadic parameter pack.

First, create the template class...

// Can be incomplete; only specialized versions will be instantiated...
template<typename... Args> class Menu;

Now, create a specialization for the end of the menu chain (no submenus)...

template<typename T>
class Menu<T>
{
public:
// No submenu in this specialization
using item_type = T;
std::vector<item_type> items;
...
};

Lastly, create a specialization for the Menu instances that will have submenus...

template<typename Head, typename... Tail>
class Menu<Head, Tail...>
{
public:
Menu<Tail...> submenu;
using item_type = Head;
std::vector<item_type> items;

...
};

This is a simplified version of your class for brevity but the same principle still applies if you add a nested Option class.

You can use a similar technique to recurse through the submenus by overloading a non-member function...

template<typename T>
void
print_menu(Menu<T> const& menu)
{
for(auto i : menu.items) {
std::cout << i << std::endl;
}
}

template<typename... T>
void
print_menu(Menu<T...> const& menu)
{
for(auto i : menu.items) {
std::cout << i << std::endl;
}
print_menu(menu.submenu);
}

int
main(int, char*[])
{
Menu<int, std::string> menu{};
menu.items.emplace_back(1);
menu.submenu.items.emplace_back("42");
print_menu(menu);
...
}

Update: A possible implementation of the choose() functionality could use a visitor pattern. You would need to provide a type that overloads operator() for each type contained in your menu (in this case, int and std::string) ...

struct ChooseVisitor
{
void operator()(std::string const& string_val) const
{ /* Do something when a string value is chosen */ }

void operator()(int int_val) const
{ /* Do something when an int value is chosen */ }
};

Similar to the print_menu function, you could define a couple of choose_menu function overloads ...

template<typename... Types, typename Visitor>
void
choose_menu(Menu<Types...> const& menu, Visitor const& visitor)
{
for(auto i : menu.items) {
if(/* is menu item chosen? */) {
visitor(i);
return;
}
}
choose_menu(menu.Submenu, visitor);
}

template<typename Type, typename Visitor>
void
choose_menu(Menu<Type> const& menu, Visitor const& visitor)
{
for(auto i : menu.items) {
if(/* is menu item chosen? */) {
visitor(i);
return;
}
}
}

This would be used like so ...

Menu<int, std::string> menu{};
...
choose_menu(menu, ChooseVisitor{});

It's a bit difficult to derive what you had in mind for your choose() function but you should be able to adapt the above to suit most scenarios.

Expand variadic template template parameters for use in e.g. std::variant<T...>

Since all your queues use the same template (queue<...>), you don't need a template template parameter (in which, by the way, the name of the nested parameter (T in your case) is ignored). You just need a type pack: typename ...T.

I also got rid of the variant array, and instead opted to iterate over the arguments directly, using a lambda in a fold expression. Though this makes extracting the return value harder, so I've put it into an optional:

#include <array>
#include <optional>
#include <variant>

template <typename T>
struct queue
{
T queue_[100];

T receive()
{
return queue_[0];
}
};

template <typename ...P>
std::variant<P...> multireceive(queue<P> &... args)
{
std::optional<std::variant<P...>> ret;

while (true)
{
// For each `args...`:
([&]{
// Do something with `args` (the current queue).
if (args.receive())
{
ret.emplace(args.queue_[0]);
return true; // Stop looping over queues.
}
return false;
}() || ...);

if (ret)
break;
}

return *ret;
}

int main()
{
queue<int> a;
queue<float> b;

std::variant<int, float> var = multireceive(a, b);
}


Related Topics



Leave a reply



Submit