6

If I have a type that is std::is_nothrow_move_constructible and I need to store it in a std::any or std::variant, which one would you recommend to use and why? Which one will give the least overhead? Edit: What are the different use cases for std::variant vs std::any?

class MyType
{
public:
   MyType(const MyType&) = default;
   MyType(MyType&&) = default;
   MyType() = default;
};

int main(int argc, char* argv[])
{
   static_assert(std::is_nothrow_move_constructible<MyType>::value, "Not move constructible");
   return 0;
}
Dev Null
  • 4,731
  • 1
  • 30
  • 46
0xBADF00
  • 1,028
  • 12
  • 26
  • 10
    They are used for different purposes. – Rinat Veliakhmedov Sep 05 '17 at 10:55
  • @RinatVeliakhmedov Would you mind to elaborate. I am not sure of the different use cases. – 0xBADF00 Sep 05 '17 at 11:01
  • 3
    Use std::variant when you know the types that can be stored (basically a safer union), std::any when you don't. – ralismark Sep 05 '17 at 11:04
  • 1
    to extend @ralismark's point: `std::any` is basically a wrapper of useful operations around `void*` – Caleth Sep 05 '17 at 11:06
  • 1
    For `std::variant` you need to enumerate possible types. If you know all of them it would be preferred, because you are able to handle all of them. (see: `variant::visit()`) However, for `std::any` you should provide a default case for "unexpected" types. – titapo Sep 05 '17 at 11:09
  • `MyType` in question is not CopyConstructible, therefore it cannot be stored in a `std::any`. – cpplearner Sep 05 '17 at 11:49
  • @cpplearner Yes you are right, I will update my question. – 0xBADF00 Sep 05 '17 at 12:05
  • 1
    Any and variant do completely different things. Use whichever models your problem best. – Kerrek SB Sep 05 '17 at 12:09

3 Answers3

9

If you know the types, use std::variant. In that case you know all the possible types, and use std::variant::visit() to apply the visitor to the variants.

If you don't, use std::any. You can think about this when dealing with types that are generic. In other words, when the types are not known apriori.


Which one will give the least overhead?

std::variant, cannot use the heap. It's not allowed to. There is no way to actually have a structure containing a possible variant of itself without a dynamic allocation, as such a structure can easily be shown to be infinite in size if statically declared. Source.

In contrast, std::any may do so. As a result, the first will have faster construction and copy operations.

gsamaras
  • 71,951
  • 46
  • 188
  • 305
  • I'm curious, how do you use a `void*` or rather `std::any` if you do not even know its possible types? – Passer By Sep 05 '17 at 11:25
  • @PasserBy check this [example](https://stackoverflow.com/documentation/c%2b%2b/7894/stdany#t=201709051128521966086). However I see your point. Maybe that part of my answer confuses the reader and should be removed. What do you think? – gsamaras Sep 05 '17 at 11:30
  • Looking at the example, IMO a more compelling reason to use `std::any` would be precisely because it uses variable a amount of memory, since the type is (apparently) ultimately known anyways. – Passer By Sep 05 '17 at 11:33
  • In my example I am refering to a type that is nothrow move constructible, these types should not be allocated on the heap in a std::any according to cppreference. "Implementations are encouraged to avoid dynamic allocations for small objects, but such an optimization may only be applied to types for which std::is_nothrow_move_constructible returns true." So in this case which one gives more overhead? – 0xBADF00 Sep 05 '17 at 11:51
  • @0xBADF00 I updated my answer. As I said I agree with what you said, but in general my answer holds. – gsamaras Sep 05 '17 at 18:32
  • std::any can't be used with non-copyable types. There is no constructor or emplace call that doesn't require is_copy_constructible and takes the type to be stored as a parameter. So this doesn't answer the question... – Jaime Dec 03 '20 at 22:07
2

std::any allocates the storage for the value from the heap. Whereas std::variant does not use the heap, hence it is more efficient in terms of construction and copy and more cache friendly because the value stored directly in the object and it does not involve indirection and virtual functions to access it.

The interface of std::variant is richer and easier to use.

std::any is useful only when std::variant cannot be used for some reason.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • Check [this](https://stackoverflow.com/a/45419725/4832499) simple implementation of `std::variant`. As far as I can see, there is no way around a lookup table / virtual function call for `std::visit`, and one way or another, there will be an indirection. TLDR cache friendliness doesn't sound right – Passer By Sep 05 '17 at 11:37
  • 1
    @PasserBy Your statement is false, visitation does not require using any virtual functions. – Maxim Egorushkin Sep 05 '17 at 11:38
  • Can you give an example of how to implement `std::visit` without any form of indirection? I genuinely do not know of a way – Passer By Sep 05 '17 at 11:42
  • 1
    @PasserBy It is trivial: `switch(variant.index()) { case 0: visitor(std::get<0>(variant); break; ...}`. No virtual functions involved. `std` implementation is more involved, look it up, still no virtual functions. – Maxim Egorushkin Sep 05 '17 at 11:45
  • 1
    Is this really true "std::any allocated the storage for the value from the heap". As far as I have understood it, it should not do this for small types that is nothrow move constructible. – 0xBADF00 Sep 05 '17 at 11:48
  • 1
    @0xBADF00 It may have a small value optimization, in the same spirit as `std::string` does small string optimization. In the general case, however, it allocates the storage from the heap. – Maxim Egorushkin Sep 05 '17 at 11:51
  • @MaximEgorushkin I took the time to go through the gcc implementation of `std::variant`, and __yes it needs a lookup table__, akin (almost identical) to that of my link above. The lookup table is aptly named `_S_vtable`. BTW switch will never work because case labels are baked in, while `std::variant` have a variable amount of types. – Passer By Sep 06 '17 at 00:34
  • 1
    @PasserBy I am not sure about the reason they implemented it using a lookup table. However, I stand by my claims: no virtual functions are involved in variant implementation, the value stored inline in the object. Whereas with `any` any access to value involves a virtual call. – Maxim Egorushkin Sep 06 '17 at 11:22
  • @MaximEgorushkin That the value is stored within the object was never disputed, and neither does it conflict with a function call going though a lookup table which essentially equates to a (hand rolled) virtual call. As for the reason, I reiterate that I honestly do not know of another way to implement it in another way. Again, check the linked answer above if you are interested in the implementation gcc and Yakk chose. – Passer By Sep 06 '17 at 11:26
  • @PasserBy IMO it should be possible to implement visitation without the lookup table. I cannot check that now because I do not have access to a computer this week. – Maxim Egorushkin Sep 06 '17 at 11:28
2

For reasons of type safety, loose coupling and ease of maintenance:

prefer variant over any if you possibly can.

Because in essence, std::any is sugar-coating around a cloneable std::unique_ptr<void, some_deleter>.

Which has a little bit of protection around bad casts (it'll throw rather than crash, which in many programs amounts to the same thing).

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • Wrong. Use of deleter is implementation details. Inappropriate use of the nominal types leaks underlying abstraction. As a side effect of wrong use, it may be suffered from ABI problems in practice. – FrankHB Mar 13 '18 at 05:33
  • @FrankHB perhaps I should have said “semantically equivalent to” rather than “in essence, is a” – Richard Hodges Mar 13 '18 at 08:21
  • That is still false, usually. They are semantically equivalent, well, on which level of abstraction? The "essence", if any, can be something like "type-erased object", but this cannot be expressed directly by C++. The equivalence occurs only before the C++ code is written, where neither one is the sugar of the other. – FrankHB Mar 14 '18 at 04:47