使用assert检查std::variant复杂类型编译失败,求可移植解决方案
可移植解决std::variant结合assert的编译/链接问题
问题概述
尝试用assert检查std::variant是否持有某一复杂嵌套类型时,在不同编译器下出现编译失败或链接错误:
- 原代码在gcc-10、clang-7、clang-14中编译报错
- 改用类型别名后gcc-10编译通过,但clang系列出现标准库符号未定义的链接错误
原代码
#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; }
错误原因分析
1. 编译失败原因
assert是预处理器宏,宏展开时会将参数中的逗号视为宏参数分隔符。原代码中std::holds_alternative的模板参数typename Foo<K, V>::List::iterator包含逗号K, V,导致宏误判为多个参数,触发"too many arguments"错误,进而被编译器识别为未声明的标识符。
2. 链接错误原因
clang编译时未正确链接C++标准库:
- 若使用
clang而非clang++驱动,默认仅链接C标准库,缺少C++标准库的异常处理、RTTI等符号 - 未明确指定C17及以上标准(
std::variant是C17特性),导致标准库符号未正确生成
可移植解决方案
步骤1:修复宏参数逗号问题
两种可选方式:
方式A:使用类型别名简化模板参数
提前用using定义类型别名,避免在宏参数中出现嵌套模板的逗号:
using Iter = typename Foo<K, V>::List::iterator; assert(std::holds_alternative<Iter>(var));
方式B:用括号包裹断言表达式
将整个std::holds_alternative调用用括号包裹,让宏将其视为单个参数:
assert( (std::holds_alternative<typename Foo<K, V>::List::iterator>(var)) );
步骤2:修复链接错误
编译时需满足:
- 使用C++编译器驱动:
clang++或g++,而非clang/gcc - 指定C++17及以上标准:添加编译选项
-std=c++17或更高版本 - 若手动指定链接库,需添加
-lstdc++(针对GNU标准库)或-lc++(针对LLVM libc++)
修正后的完整代码
#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 { // 使用类型别名避免宏参数逗号问题 using Iter = typename Foo<K, V>::List::iterator; assert(std::holds_alternative<Iter>(var)); return std::get<Iter>(var); } }; int main() { Foo<int, int>::Item item; Bar<int, int> bar; bar.func(item.var); return 0; }
编译命令示例
# clang++编译 clang++ -std=c++17 your_file.cpp -o your_program # g++编译 g++ -std=c++17 your_file.cpp -o your_program
内容的提问来源于stack exchange,提问作者for_stack
相关产品推荐
相关产品推荐

