2

The following code uses assert to check if a std::variant holds some specific type, i.e. a complex type. However, it fails to compile with gcc-10, clang-7, and clang-14.

#include <cassert>
#include <list>
#include <variant>

template <typename K, typename V>
class Foo {
public:
    struct Item;

    using List = std::list<Item>;

    using Var = std::variant<V, typename List::iterator>;

    struct Item {
        K k;
        Var var;
    };
};

template <typename K, typename V>
class Bar {
public:
    auto func(typename Foo<K, V>::Var &var)
        -> typename Foo<K, V>::List::iterator {

        assert(std::holds_alternative<typename Foo<K, V>::List::iterator>(var));

        return std::get<typename Foo<K, V>::List::iterator>(var);
    }
};

int main() {
    Foo<int, int>::Item item;
    Bar<int, int> bar;
    bar.func(item.var);
    return 0;
}

The compilers complain that assert is not declared:

t.cpp:25:55: error: too many arguments provided to function-like macro invocation
        assert(std::holds_alternative<typename Foo<K, V>::List::iterator>(var));
                                                      ^
/usr/include/assert.h:92:11: note: macro 'assert' defined here
#  define assert(expr)                                                  \
          ^
t.cpp:25:9: error: use of undeclared identifier 'assert'; did you mean '__assert'?
        assert(std::holds_alternative<typename Foo<K, V>::List::iterator>(var));
        ^~~~~~
        __assert
/usr/include/assert.h:81:13: note: '__assert' declared here
extern void __assert (const char *__assertion, const char *__file, int __line)
            ^
t.cpp:25:9: warning: expression result unused [-Wunused-value]
        assert(std::holds_alternative<typename Foo<K, V>::List::iterator>(var));
        ^~~~~~
t.cpp:25:9: warning: expression result unused [-Wunused-value]
        assert(std::holds_alternative<typename Foo<K, V>::List::iterator>(var));
        ^~~~~~
t.cpp:36:9: note: in instantiation of member function 'Bar<int, int>::func' requested here
    bar.func(item.var);
        ^
2 warnings and 2 errors generated.

However, if I change the assert call to the following:

using Iter = typename Foo<K, V>::List::iterator;

assert(std::holds_alternative<Iter>(var));

gcc-10 compiles successfully, while clang-7 and clang-14 fails with some strange error messages:

/usr/bin/ld: /tmp/t-ce3ab8.o: in function `__clang_call_terminate':
t.cpp:(.text.__clang_call_terminate[__clang_call_terminate]+0x2): undefined reference to `__cxa_begin_catch'
/usr/bin/ld: t.cpp:(.text.__clang_call_terminate[__clang_call_terminate]+0x7): undefined reference to `std::terminate()'
/usr/bin/ld: /tmp/t-ce3ab8.o: in function `std::__throw_bad_variant_access(char const*)':
t.cpp:(.text._ZSt26__throw_bad_variant_accessPKc[_ZSt26__throw_bad_variant_accessPKc]+0x12): undefined reference to `__cxa_allocate_exception'
/usr/bin/ld: t.cpp:(.text._ZSt26__throw_bad_variant_accessPKc[_ZSt26__throw_bad_variant_accessPKc]+0x39): undefined reference to `__cxa_throw'
/usr/bin/ld: /tmp/t-ce3ab8.o: in function `std::bad_variant_access::~bad_variant_access()':
t.cpp:(.text._ZNSt18bad_variant_accessD2Ev[_ZNSt18bad_variant_accessD2Ev]+0x11): undefined reference to `std::exception::~exception()'
/usr/bin/ld: /tmp/t-ce3ab8.o: in function `std::exception::exception()':
t.cpp:(.text._ZNSt9exceptionC2Ev[_ZNSt9exceptionC2Ev]+0xf): undefined reference to `vtable for std::exception'
/usr/bin/ld: /tmp/t-ce3ab8.o: in function `std::bad_variant_access::~bad_variant_access()':
t.cpp:(.text._ZNSt18bad_variant_accessD0Ev[_ZNSt18bad_variant_accessD0Ev]+0x1e): undefined reference to `operator delete(void*)'
/usr/bin/ld: /tmp/t-ce3ab8.o:(.data.rel.ro._ZTISt18bad_variant_access[_ZTISt18bad_variant_access]+0x0): undefined reference to `vtable for __cxxabiv1::__si_class_type_info'
/usr/bin/ld: /tmp/t-ce3ab8.o:(.data.rel.ro._ZTISt18bad_variant_access[_ZTISt18bad_variant_access]+0x10): undefined reference to `typeinfo for std::exception'
/usr/bin/ld: /tmp/t-ce3ab8.o:(.data.DW.ref.__gxx_personality_v0[DW.ref.__gxx_personality_v0]+0x0): undefined reference to `__gxx_personality_v0'
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Is there any portable solution to this problem?

for_stack
  • 21,012
  • 4
  • 35
  • 48

1 Answers1

3

Macros have some pitfalls, not only for the writer of the macro, but also for the user. , is special, because it delimits parameters to function like macros, hence can cause issues when it appears in the parameters. Here, adding brackets helps:

assert((std::holds_alternative<typename Foo<K, V>::List::iterator>(var)));
    // ^                                                               ^
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Great! This solves the original problem. However, why the solution I proposed makes clang complain some strange error? – for_stack Aug 24 '22 at 09:22
  • @for_stack cannot reproduce it https://godbolt.org/z/vM6a9WsTG – 463035818_is_not_an_ai Aug 24 '22 at 09:24
  • It's strange. I run the code in 3 docker containers, one installs gcc-10, one installs clang-10, and the other installs clang-14. Gcc works while clang fails, even with your solution. – for_stack Aug 24 '22 at 09:31
  • @for_stack I can get it to compile also with clang https://godbolt.org/z/vaE9r9fnn. Perhaps try to reproduce it in godbolt. – 463035818_is_not_an_ai Aug 24 '22 at 09:34
  • 1
    @for_stack The linker errors you're seeing with `clang` are possibly due to `clang` linking with `libc++` by default. You might want to try adding `-stdlib=libstdc++` to your link command. Just a guess though. – G.M. Aug 24 '22 at 09:35
  • @G.M. points out the problem and solution. I also googled [this](https://stackoverflow.com/questions/6045809/link-error-undefined-reference-to-gxx-personality-v0-and-g). Looks like my clang is not installed correctly. Still not sure why it fails in this case, while it can compile other code correctly. Anyway, thanks guys! – for_stack Aug 24 '22 at 09:39