4

I've got code that can be simplified to

std::variant<float, int> v[2] = foo();
int a = std::get<decltype(a)>(v[0]);
float b = std::get<decltype(b)>(v[1]);

Obviously this can go throw if foo() returns the wrong variants, but that's not my problem here. (The real code has a catch). My problem is that the decltype(a) violates the Don't Repeat Yourself principle.

Is there a cleaner way to initialize a and b, and still throw if the types do not match expectations? In particular, I don't want a static_cast<int>(std::get<float>(v)) if the variant contains a float while I'm trying to initialize an int.

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
MSalters
  • 173,980
  • 10
  • 155
  • 350
  • 9
    Is this feasible: `auto a = std::get(v[0]);` ? – paolo Jul 12 '22 at 15:56
  • 1
    "`decltype(a)` violates the Don't Repeat Yourself principle." How so? You aren't repeating the type of `a` – Caleth Jul 12 '22 at 15:59
  • You can use auto as @paolo suggested or index the types with numbers, i.e. `int a = std::get<1>(v[0])` – Dominik Rafacz Jul 12 '22 at 15:59
  • 2
    @DominikRafacz: Magic numbers feel worse than DRY violations to me. *shrugs*. – ShadowRanger Jul 12 '22 at 16:01
  • @Caleth: But you are repeating `a` itself. It's not a major violation (changing the declared type of `a` only requires changing one piece still), but it does mean you need to repeat the name of `a` (which in real, vaguely self-documenting code is likely much longer) twice even though it's logically only relevant the once, which is a pain. – ShadowRanger Jul 12 '22 at 16:02
  • @ShadowRanger yeah, I completely agree. I only suggested that as I am also not sure what OP meant by DRY violation. – Dominik Rafacz Jul 12 '22 at 16:08
  • @paolo: Good comment, but in the real code the objects being initialized are class members. – MSalters Jul 12 '22 at 16:16
  • 1
    Ah, **now** you tell us :) – Paul Sanders Jul 12 '22 at 16:18
  • There's a parallel between this and the idiomatic C `int *a = malloc(sizeof *a);`, where the latter doesn't seem at all unreadable or unDRY to me. Maybe it's the extra token soup in C++ that makes this so unpalatable. Because I find myself deterred as well. – StoryTeller - Unslander Monica Jul 12 '22 at 16:40

3 Answers3

12

You could wrap your call to get in a template that implicitly converts to the target type.

template<typename... Ts>
struct variant_unwrapper {
    std::variant<Ts...> & var;
    template <typename T>
    operator T() { return std::get<T>(var); }
};

See it on coliru

Caleth
  • 52,200
  • 2
  • 44
  • 75
  • But: https://coliru.stacked-crooked.com/a/db6fb26632afd38e (if that's a problem) – Paul Sanders Jul 12 '22 at 16:34
  • 3
    @PaulSanders - You didn't actually initialize `v[2]` with a `short`. There isn't an actual problem in that snippet. – StoryTeller - Unslander Monica Jul 12 '22 at 16:36
  • 2
    @PaulSanders https://coliru.stacked-crooked.com/a/f54c84077ae8fa73 – Marek R Jul 12 '22 at 16:43
  • @StoryTeller-UnslanderMonica Good point – Paul Sanders Jul 12 '22 at 17:39
  • Hi, thanks for your answer. I tried your coliru code on compiler explorer: https://godbolt.org/z/cPYhz9n6z, it compiles with GCC 13.1, but doesn't compile with Clang 16.0.0 (released March 18 2023, nearly a year after your answer). Do you think this is due to unfinished clang feature support, or a difference of opinion in compilers? – Paradox Jul 20 '23 at 00:54
  • @Paradox for some reason clang needs a deduction guide for `variant_unwrapper`, [see here](https://godbolt.org/z/f9r6GsKea) – Caleth Jul 20 '23 at 07:58
10

IMO it would be nice to allow template deduction to take over, so providing a helper function should do the job:

template<typename T, typename...VariantParams>
void get_from(const std::variant<VariantParams...>& v, T& value)
{
    value = ::std::get<T>(v);
}

int a;

get_from(v[0], a);
Marek R
  • 32,568
  • 6
  • 55
  • 140
5

As @paulo says in the comments, seems like the DRY solution is to use auto for the declaration, changing:

int a = std::get<decltype(a)>(v[0]);

to:

auto a = std::get<int>(v[0]);

You only name the type (int) and the variable (a) once each. Doesn't work if you separate declaration and initialization, so you'd still need:

int a;
...
a = std::get<decltype(a)>(v[0]);

in that case, but if you write all your C++ code deferring declarations until the point of definition, it's not needed often.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271