-
Notifications
You must be signed in to change notification settings - Fork 15.2k
Description
By giving the following codes
#include <string>
#include <type_traits>
template <class Tp, class = decltype(Tp{})>
auto test_default_constructible(int) -> std::true_type;
template <class...> auto test_default_constructible(float) -> std::false_type;
template <class Tp>
constexpr bool is_default_constructible_v =
decltype(test_default_constructible<Tp>(0))::value;
struct NoDefaultConstructible {
NoDefaultConstructible(int) {}
};
template <class Tp> struct ABC {
ABC(std::initializer_list<int>, Tp = Tp{}) {}
};
int main() {
constexpr auto v = is_default_constructible_v<ABC<NoDefaultConstructible>>;
static_assert(!v);
}
It's a simple type trait implementaion that using the old fashion SFINAE trick. In gcc it compiles (the value v is false). However, it will cause a hard error when using Clang.
Here is a compiler explorer reproducer: https://godbolt.org/z/84378b3Tx
We can see the same problem if we change to use the modern concept and constraint stuff: https://godbolt.org/z/ve8GE9K8x
#include <type_traits>
#include <string>
namespace test {
template <typename Tp>
constexpr bool
is_implicitly_default_constructible_v = requires {
Tp{};
};
} // namespace test
struct NonDefaultConstructible {
NonDefaultConstructible(int) {}
};
template <class Tp> class ABC {
public:
ABC() requires test::is_implicitly_default_constructible_v<Tp> {}
ABC(std::initializer_list<int>, const Tp& v = Tp()) : obj(v) {}
Tp obj;
};
int main() {
constexpr auto v = test::is_implicitly_default_constructible_v<ABC<NonDefaultConstructible>>;
static_assert(!v);
}
In both two examples, Clang complains an error that there is no matching constructor for Tp
in ABC(std::initializer_list<int>, const Tp& v = Tp())
. While I think the correct behaviour is fallback to the another overload of test_default_constructible
(in the SFINAE example), or the requires expression returns false (in the concept and constraint example).
Why do I think this is a Clang bug? It not just because of the different behaviour than gcc. It is indeed possible to make a hard error during the SFINAE, if we have the wrong syntax, or the error occurs outside the immediate contex. (I am not an expert, forgive me if I'm wrong). However, this case doesn't apply to any of them.
- If this default argument belongs to the immediate context, then it shouldn't be a hard error. SFINAE will work, and
is_implicitly_default_constructible_v
is evaulated as false. - If this outside the immediate context, then it should choose the first
test_default_constructible
overload. The type trait will mistakenly return true. It should only lead an error in the following codes (i.e., if we use this type trait to choose the proper code branch, then the wrong type trait result will lead a wrong code branch). The type trait itself shoule compile (although its result is wrong).
Futhermore, We have a lot of codes that similar to my examples in the libstdc++. For example, in std::basic_string
, we have (in https://gcc.gnu.org/onlinedocs/gcc-15.1.0/libstdc++/api/a00650_source.html line:798)
basic_string(initializer_list<_CharT> __l, const _Alloc& __a = _Alloc())
And we also have the similar type traits in the libstdc++, https://gcc.gnu.org/onlinedocs/gcc-15.1.0/libstdc++/api/a00248_source.html line:1362
template<typename _Tp>
constexpr bool __is_implicitly_default_constructible_v
= requires (void(&__f)(_Tp)) { __f({}); };
template<typename _Tp>
struct __is_implicitly_default_constructible
: __bool_constant<__is_implicitly_default_constructible_v<_Tp>>
{ };
When we have some std::basic_string
or other containers with a custom allocator that doesn't have a default constructor. Then it's no doubt some promblems will occur. And this issue would be a minimal reproducer (Actually we encountered the promblem already, that's why I create this issue).