Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using std::visit on a class inheriting from std::variant - libstdc++ vs libc++

Consider the following code snippet:

struct v : std::variant<int, std::vector<v>> { };

int main()
{
    std::visit([](auto){ }, v{0});
}
  • clang++ 7 with -stdlib=libc++ -std=c++2a compiles the code;

  • g++ 9 with -std=c++2a fails to compile the code, with the following error:

    /opt/compiler-explorer/gcc-trunk-20180711/include/c++/9.0.0/variant:94:29: error: incomplete type 'std::variant_size' used in nested name specifier

     inline constexpr size_t variant_size_v = variant_size<_Variant>::value;
    
                             ^~~~~~~~~~~~~~
    

live example on godbolt.org


  • Are both implementations conforming to the Standard?

  • If not, what implementation is correct here, and why?

like image 516
Vittorio Romeo Avatar asked Jul 12 '18 15:07

Vittorio Romeo


3 Answers

[variant.visit] in C++17 doesn't use variant_size_v, but it does in the current working draft as a result of an editorial change. I don't see any indication that LWG reviewed the change before it went in, but it has looked at this part of the standard several times since then and has yet to object to it, so I'm going to postulate that it is in fact required.

Meanwhile, LWG issue 3052, which has been referred to LEWG, would explicitly require std::variant. When that issue is resolved - one way or the other - it should resolve this too.

like image 87
T.C. Avatar answered Oct 18 '22 04:10

T.C.


Looks like it is a bug in gcc implementation. According to cppreference, it is called as if calling invoke on a std::get. std::get<> is defined for anything which is convertible to std::variant (since it accepts a std::variant argument by forwarding reference). Your structure is convertible to std::variant, and so std::get itself works on your structure in gcc.

The fact that the gcc implementation chose to use a std::variant_size as part of its implementation of visit is their implementation detail, and the fact that it doesn't (and shouldn't) work for your struct is irrelevant.

Conclusion: It is a bug in gcc due to an oversight in implementation.

like image 22
SergeyA Avatar answered Oct 18 '22 03:10

SergeyA


I came across this issue as well recently. I kind of came up with a workaround which basically specialises variant_size and variant_alternative for the class that inherits from the variant..

link on godbolt

Its not pretty and it injects stuff into the std namespace. I'm not a metaprogramming expert (yet!) so its something I hacked together. Maybe someone else can improve on this?

#include <variant>
#include <string>
#include <vector>
#include <iostream>

#include <utility>
#include <type_traits>



using var = std::variant<int, bool, float, std::string>;

struct myvar : public var {
    using var::var;
    using var::operator=;

};

namespace std{

    template<>
    struct variant_size<myvar> : variant_size<var> {
    };

    template<std::size_t I>
    struct variant_alternative<I,myvar> :  variant_alternative<I,var> {
    };
}

int main(){

    constexpr int vs = std::variant_size<var>::value;

    myvar s = std::string{"boo!"}; 
    std::visit([](auto&& e){std::cout << e << "\n";}, s);
    std::cout << vs;
}

like image 3
Daniel Dokipen Elliott Avatar answered Oct 18 '22 04:10

Daniel Dokipen Elliott