I have a function which creates an object and inserts it into a container. In most cases, the object type is the same as that of the container elements. Then, I don't want to have to specify the object type. But for containers holding std::variant
, I want to be able to specify the object type as the first template parameter. Here's the non-working example:
template<typename T>
using remove_qualifiers = std::remove_cv_t<std::remove_reference_t<T>>;
template<typename T>
using IteratorType = decltype( std::declval<remove_qualifiers<T>>().end() );
template<typename T>
using ElementType = decltype( *( std::declval<remove_qualifiers<T>>().end() ) );
template<typename E = ElementType<T>, typename T, typename ... Args>
IteratorType<T> insert( T& someContainer, Args&& ... ){
IteratorType<T> it { someFindFunc() };
return someContainer.insert(it, E{std::forward<Args>(args) ...});
}
The problem is, that the default type for E
is based on T
, which at this point is not declared yet. But if I change the order, then calling the function becomes awkward.
How can I make this pattern work, so that I can call the function both with and without specifying E
?
I guess this is likely a case of not finding the right search terms. I looked at this, this, this, and this one, and they don't appear to be what I'm looking for.
CodePudding user response:
Consider wrap it into a class template so that you don't need to worry about specifying the type of the container.
template <typename T>
class Inserter {
public:
Inserter(T& someContainer): container{someContainer} {}
using IteratorType = decltype( std::declval<remove_qualifiers<T>>().end() );
template <typename E = ElementType<T>, typename... Args>
IteratorType insert(Args&&... args) {
IteratorType it = container.begin(); // just for sample
return container.insert(it, E{std::forward<Args>(args) ...});
}
private:
T& container;
};
Then,
std::vector<std::variant<int, double>> v;
Inserter{v}.insert<int>(1);
Inserter{v}.insert<double>(1.1);
CodePudding user response:
If you are not using some sort of emplace, you should just move construction to the caller. For simplicity I'm using push_back
instead of insert
.
template <typename T, typename Value>
inline auto insert(T& container, Value&& value) {
container.push_back(std::forward<Value>(value));
}
But let's assume you are really constructing the type in place.
template <typename T, typename... Args>
inline auto emplace(T& container, Args&&... args) {
container.emplace_back(std::forward<Args>(args)...);
}
This is possibly all you need. You can construct any type, variants as well.
struct A { int n; };
struct B { int n; };
auto a = std::vector<A>{};
emplace(a, 100);
auto b = std::vector<B>{};
emplace(b, 100);
auto ab = std::vector<std::variant<A, B>>{};
emplace(ab, std::in_place_type_t<A>{}, 100); // tell the type
emplace(ab, 100); // ambigious, compile error
As for your specific use-case, there is no way of deducing some sort of default type of a variant. You always have to tell explicitly what to construct.
You can reinvent the wheel, create an overload for variants and then hide std::in_place_type_t
. But generally this problem has been solved by the STL for any type that can be constructed in place.