How to visit on multiple variants with restricted to same type

129 views Asked by At

I have a question about using a generic visitor with exclusion of non-equal types. Suppose I have the following:

using StorageT = std::variant<std::vector<int>, std::vector<double>>;

StorageT process_data(const StorageT &lhs, const StorageT &rhs) {
    return std::visit([&](auto &&l, auto &&r){
        return kernel(l, r);   // some templated kernel 
    },
    lhs, rhs);
}

Externally, I verify that the underlying stored type for variants lhs and rhs match. I'd like to write a generic visitor, without code being generated for all combinations of lhs and rhs where the underlying variant types are not the same.

As it stands, all combinations of the two underlying variant types are generated. Inside the lambda, I could write

if constexpr(std::is_same_v<Tl, TR>) {
    return kernel( ... );
} else {
    // Do nothing
    // static_assert(false);
    // Throw exception
}

Since all combinations of lhs and rhs are generated, the above code either a) complains about no return on the mismatch of types, b) fails due to the static assert, or c) generated a code path for an exception which will never be thrown (due to an external check of the types).

What is the best way to avoid code generation for all cases where the underlying types for lhs does not equal rhs?

2

There are 2 answers

2
John Zwinck On BEST ANSWER

Just visit one of the types and use that to determine the other type:

StorageT process_data(const StorageT &lhs, const StorageT &rhs) {
    return std::visit([&](auto &&l){
        auto &r = std::get<std::remove_cvref_t<decltype(l)>>(rhs);
        return kernel(l, r); 
    },
    lhs);
}
3
wohlstad On

You can use std::unreachable (defined in the <utility> header).
From the documentation:

Invokes undefined behavior. An implementation may use this to optimize impossible code branches away (typically, in optimized builds) or to trap them to prevent further execution (typically, in debug builds).

(emphasys is mine)

Since the compiler can assume undefined behavior will never happen, it is a classic case for optimizing away the piece of code that you never expect to be needed.

So your code can be:

#include <utility>   // for std::unreachable

// ..

if constexpr(std::is_same_v<Tl, TR>) {
    return kernel( /*...*/ );
} else {
    std::unreachable();
}