clang++ 18下含requires表达式的模板函数出现未定义引用,代码是否合法?
问题描述
我编写了一段测试代码,在g++ 13.2及clang++ 17及以下版本均可正常编译链接,但使用clang++ 18编译时失败,出现未定义引用错误。通过nm工具查看发现,clang++ 18将requires条件纳入了导出符号名称中,而旧版本并未如此。请问这段代码是否符合C++标准?
测试代码
test.cpp
#include "test.hpp" #include <optional> #include <stdio.h> #include <string> template<class T> requires(!std::is_same_v<T, std::string>) std::optional<T> myFunction(std::string arg) { return std::optional<T>{}; } template std::optional<int> myFunction<int>(std::string arg);
test.hpp
#pragma once #include <optional> #include <string> template<class T> std::optional<T> myFunction(std::string arg);
main.cpp
#include "test.hpp" #include <optional> int main() { std::optional<int> foobar = myFunction<int>("foobar"); }
复现步骤
#!/bin/sh -x clang++-18 --version clang++-18 -std=c++20 -c test.cpp -o test-clang.o clang++-18 -std=c++20 -c main.cpp -o main-clang.o clang++-18 main-clang.o test-clang.o -o main-clang
编译报错信息
/usr/bin/ld: main-clang.o: in function
main': main.cpp:(.text+0x2f): undefined reference tostd::optionalmyFunction (std::__cxx11::basic_string<char, std::char_traits , std::allocator >)'
clang++-19: error: linker command failed with exit code 1 (use -v to see invocation)
nm工具对比结果
clang++ 17生成的符号
$ nm --demangle test-clang-17.o | grep -i myFunc 0000000000000000 W std::optional<int> myFunction<int>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)
clang++ 18生成的符号
$ nm --demangle test-clang-18.o | grep -i myFunc 0000000000000000 W std::optional<int> myFunction<int>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) requires !(std::is_same_v<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >)
解答
这段代码不符合C++标准。
C++标准要求:模板的所有声明(包括头文件中的声明)必须与定义保持完全一致,包括模板的约束条件(requires子句)。在你的代码中,头文件test.hpp里的myFunction模板声明没有包含requires(!std::is_same_v<T, std::string>)约束,而test.cpp中的模板定义却带有这个约束,这属于声明与定义不匹配的情况。
旧版本的编译器可能没有严格执行这一规则,所以能正常链接;但clang++ 18开始严格遵循C++标准,将requires约束作为符号名称的一部分,导致头文件声明生成的符号(无约束)与cpp定义生成的符号(带约束)不匹配,最终触发链接错误。
修复方法
将requires约束添加到头文件的模板声明中,确保声明与定义一致:
修改后的test.hpp:
#pragma once #include <optional> #include <string> #include <type_traits> template<class T> requires(!std::is_same_v<T, std::string>) std::optional<T> myFunction(std::string arg);
这样修改后,clang++ 18就能生成匹配的符号,链接错误会消失。
内容的提问来源于stack exchange,提问作者Étienne

