Home > front end >  Default template type based on later template type
Default template type based on later template type

Time:09-12

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);

Demo

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.

  • Related