I have this test-code which compiles fine with g++ version 13.2 as well as clang++ until version 17. It fails to compile with clang++ version 18.
Is the code valid?
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");
}
How to reproduce:
#!/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
With clang++-18, this produces:
/usr/bin/ld: main-clang.o: in function `main':
main.cpp:(.text+0x2f): undefined reference to `std::optional<int> myFunction<int>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)'
clang++-19: error: linker command failed with exit code 1 (use -v to see invocation)
nm shows the issue: with clang++-18, the “requires” is part of the name of the exported symbol, whereas it should not as far as I understand.
$ 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> >)
$ 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> > >)
6
I think the requires
clause is a part of myFunction’s signature, which means it affects the mangled name of your function. Make sure to include that in test.hpp:
template<class T>
requires(!std::is_same_v<T, std::string>)
std::optional<T> myFunction(std::string arg);
I found out this change is documented in clang 18’s release notes:
The name mangling rules for function templates has been changed to
take into account the possibility that functions could be overloaded
on their template parameter lists or requires-clauses. This causes
mangled names to change for function templates in the following cases:When a template parameter in a function template depends on a previous
template parameter, such as template<typename T, T V> void f().When the function has any constraints, whether from constrained
template parameters or requires-clauses.When the template parameter list includes a deduced type – either
auto, decltype(auto), or a deduced class template specialization type.When a template template parameter is given a template template
argument that has a different template parameter list.This fixes a number of issues where valid programs would be rejected
due to mangling collisions, or would in some cases be silently
miscompiled. Clang will use the old manglings if -fclang-abi-compat=17
or lower is specified. (#48216), (#49884), and (#61273)