std::variant
has a constructor that accepts std::in_place_t<Type>, Args &&...
arguments that results in in-place construction.
{
std::cout << "in_place_type:\n";
const auto var = std::variant<callable>(std::in_place_type<callable>);
std::cout << "finished\n";
}
I wonder if it is somehow possible to in-place construct given a Factory object:
{
std::cout << "by factory:\n";
const auto var = std::variant<callable>{[] ()-> callable {return {}; }()};
std::cout << "finished\n";
}
https://godbolt.org/z/rY5WG1hGn
#include <variant>
#include <iostream>
int main(int, char **)
{
struct callable
{
callable()
{
std::cout << "callable()" << std::endl;
}
~callable()
{
std::cout << "~callable()" << std::endl;
}
};
{
std::cout << "in_place_type:\n";
const auto var = std::variant<callable>(std::in_place_type<callable>);
std::cout << "finished\n";
}
std::cout << '\n';
{
std::cout << "by factory:\n";
const auto var = std::variant<callable>{[] ()-> callable {return {}; }()};
std::cout << "finished\n";
}
}
I think it does not work in this case because std::variant(T &&t)
constructor is used with the factory, and it can not use copy elision.
I tried to elaborate on HTNW's answer (create an onbject from arguments), but get a compiler error:
#include <variant>
#include <tuple>
struct to_construct {
// must NOT exist
// template<typename F>
// to_construct(initializer<F>) { std::cout << "Foiled!\n"; }
// or (more likely)
// template<typename T>
// to_construct(T) { std::cout << "Foiled again!\n"; }
to_construct() = default;
to_construct(to_construct&&) = delete;
to_construct(to_construct const&) = delete;
};
#include <iostream>
struct callable
{
callable(int i)
{
std::cout << "callable():" << i << std::endl;
}
~callable()
{
std::cout << "~callable()" << std::endl;
}
};
template<typename T>
struct box {
T x;
template<typename F>
box(F f)
: x(f())
{}
};
template<typename F>
struct initializer {
F init;
operator auto() {
return init();
}
};
template<typename F>
initializer(F) -> initializer<F>;
template <typename ... Types, typename Factory, typename ... Args>
std::variant<box<Types>...> variant_from(Factory &&f, Args &&... args)
{
return {
initializer{
[tupleArgs = std::forward_as_tuple(args...),
f = std::forward<Factory>(f)](){
return std::apply(f, tupleArgs);
}
}
};
}
int main()
{
{
const auto var =
std::variant<to_construct>(initializer{
[]() -> to_construct {
std::cout << "Success\n";
return {};
}
});
}
{
const auto var = variant_from<callable>([](int i) { return callable(i);}, 42);
}
}
<source>: In instantiation of 'box<T>::box(F) [with F = initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >; T = callable]':
/opt/compiler-explorer/gcc-12.2.0/include/c /12.2.0/variant:283:4: required from 'constexpr std::__detail::__variant::_Uninitialized<_Type, false>::_Uninitialized(std::in_place_index_t<0>, _Args&& ...) [with _Args = {initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >}; _Type = box<callable>]'
/opt/compiler-explorer/gcc-12.2.0/include/c /12.2.0/variant:385:4: required from 'constexpr std::__detail::__variant::_Variadic_union<_First, _Rest ...>::_Variadic_union(std::in_place_index_t<0>, _Args&& ...) [with _Args = {initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >}; _First = box<callable>; _Rest = {}]'
/opt/compiler-explorer/gcc-12.2.0/include/c /12.2.0/variant:460:4: required from 'constexpr std::__detail::__variant::_Variant_storage<false, _Types ...>::_Variant_storage(std::in_place_index_t<_Np>, _Args&& ...) [with long unsigned int _Np = 0; _Args = {initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >}; _Types = {box<callable>}]'
/opt/compiler-explorer/gcc-12.2.0/include/c /12.2.0/variant:557:20: required from 'constexpr std::__detail::__variant::_Variant_base<_Types>::_Variant_base(std::in_place_index_t<_Np>, _Args&& ...) [with long unsigned int _Np = 0; _Args = {initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >}; _Types = {box<callable>}]'
/opt/compiler-explorer/gcc-12.2.0/include/c /12.2.0/variant:1448:57: required from 'constexpr std::variant<_Types>::variant(std::in_place_index_t<_Np>, _Args&& ...) [with long unsigned int _Np = 0; _Args = {initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >}; _Tp = box<callable>; <template-parameter-2-4> = void; _Types = {box<callable>}]'
/opt/compiler-explorer/gcc-12.2.0/include/c /12.2.0/variant:1419:27: required from 'constexpr std::variant<_Types>::variant(_Tp&&) [with _Tp = initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >; <template-parameter-2-2> = void; <template-parameter-2-3> = void; _Tj = box<callable>; <template-parameter-2-5> = void; _Types = {box<callable>}]'
<source>:61:5: required from 'std::variant<box<Types>...> variant_from(Factory&&, Args&& ...) [with Types = {callable}; Factory = main()::<lambda(int)>; Args = {int}]'
<source>:78:46: required from here
<source>:36:8: error: no match for call to '(initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >) ()'
36 | : x(f())
| ~^~
ASM generation compiler returned: 1
<source>: In instantiation of 'box<T>::box(F) [with F = initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >; T = callable]':
/opt/compiler-explorer/gcc-12.2.0/include/c /12.2.0/variant:283:4: required from 'constexpr std::__detail::__variant::_Uninitialized<_Type, false>::_Uninitialized(std::in_place_index_t<0>, _Args&& ...) [with _Args = {initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >}; _Type = box<callable>]'
/opt/compiler-explorer/gcc-12.2.0/include/c /12.2.0/variant:385:4: required from 'constexpr std::__detail::__variant::_Variadic_union<_First, _Rest ...>::_Variadic_union(std::in_place_index_t<0>, _Args&& ...) [with _Args = {initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >}; _First = box<callable>; _Rest = {}]'
/opt/compiler-explorer/gcc-12.2.0/include/c /12.2.0/variant:460:4: required from 'constexpr std::__detail::__variant::_Variant_storage<false, _Types ...>::_Variant_storage(std::in_place_index_t<_Np>, _Args&& ...) [with long unsigned int _Np = 0; _Args = {initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >}; _Types = {box<callable>}]'
/opt/compiler-explorer/gcc-12.2.0/include/c /12.2.0/variant:557:20: required from 'constexpr std::__detail::__variant::_Variant_base<_Types>::_Variant_base(std::in_place_index_t<_Np>, _Args&& ...) [with long unsigned int _Np = 0; _Args = {initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >}; _Types = {box<callable>}]'
/opt/compiler-explorer/gcc-12.2.0/include/c /12.2.0/variant:1448:57: required from 'constexpr std::variant<_Types>::variant(std::in_place_index_t<_Np>, _Args&& ...) [with long unsigned int _Np = 0; _Args = {initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >}; _Tp = box<callable>; <template-parameter-2-4> = void; _Types = {box<callable>}]'
/opt/compiler-explorer/gcc-12.2.0/include/c /12.2.0/variant:1419:27: required from 'constexpr std::variant<_Types>::variant(_Tp&&) [with _Tp = initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >; <template-parameter-2-2> = void; <template-parameter-2-3> = void; _Tj = box<callable>; <template-parameter-2-5> = void; _Types = {box<callable>}]'
<source>:61:5: required from 'std::variant<box<Types>...> variant_from(Factory&&, Args&& ...) [with Types = {callable}; Factory = main()::<lambda(int)>; Args = {int}]'
<source>:78:46: required from here
<source>:36:8: error: no match for call to '(initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >) ()'
36 | : x(f())
| ~^~
Execution build compiler returned: 1
https://godbolt.org/z/8MMMEe3jx
CodePudding user response:
Yes.
The factory to wrapped in a type with a conversion to the contained type, and the contained type needs to not have a constructor that would take precedence.
template<typename F>
struct initializer {
F init;
operator auto() {
return init();
}
};
template<typename F>
initializer(F) -> initializer<F>;
struct to_construct {
// must NOT exist
// template<typename F>
// to_construct(initializer<F>) { std::cout << "Foiled!\n"; }
// or (more likely)
// template<typename T>
// to_construct(T) { std::cout << "Foiled again!\n"; }
to_construct() = default;
to_construct(to_construct&&) = delete;
to_construct(to_construct const&) = delete;
};
int main() {
std::variant<to_construct, int> v;
v.emplace<to_construct>(initializer{[]() -> to_construct { std::cout << "Success\n"; return {}; }});
}
The point that the contained type cannot have a constructor matching initializer
is important: it means you cannot safely do this trick for variant
s of types that you do not control. If you need to construct some external T
in a variant
in this way, instead wrap the T
in a box
of your own control, in which case you can just add a constructor from a factory directly anyway.
// do NOT do this, because it might fail silently and weirdly
// template<typename T>
// void foo() {
// std::variant<int, T> v;
// v.emplace<1>(initializer{[]() -> T { return {}; }});
// }
template<typename T>
struct box {
T x;
template<typename F>
box(F f) : x(f()) { }
};
template<typename T>
void foo() {
std::variant<int, box<T>> v;
v.template emplace<1>([]() -> T { return {}; });
}
I.e. you can construct an object in a variant
from a factory without changing the variant
type sometimes (when you control the alternatives), but in general you may need to change some alternatives into box
s.
The issue with your expanded code based on the above is that you try to use initializer
with box
. box
does not need initializer
. It takes lambdas directly. Further, since the initializing lambda does not have to live past the construction of the variant
, it should pretty much never capture by value.
// it is convention in the C standard libraries to not bother forwarding functors
template<typename... Types, typename Factory, typename... Args>
std::variant<box<Types>...> variant_from(Factory f, Args&&... args) {
return [&]() { return f(std::forward<Args>(args)...); };
}