我是 C++ 模板的新手,刚刚学习 SFINAE 和 C++ 中的相关功能。
我正在阅读here的帖子。在那篇文章中,作者尝试实现 boost hana 的
is_valid
,用于测试对象是否满足某些属性。
这里要实现的功能非常简单:如果我可以在
.serialze()
上调用obj
,那么就调用它。否则,回退使用to_string()
.
我从那个帖子模仿的最终代码是:
template <typename UnnamedType> struct Container
{
// Let's put the test in private.
private:
// We use std::declval to 'recreate' an object of 'UnnamedType'.
// We use std::declval to also 'recreate' an object of type 'Param'.
// We can use both of these recreated objects to test the validity!
template <typename Param> constexpr auto testValidity(int /* unused */)
-> decltype(std::declval<UnnamedType>()(std::declval<Param>()), std::true_type{})
{
// If substitution didn't fail, we can return a true_type.
return std::true_type{};
}
template <typename Param> constexpr std::false_type testValidity(...)
{
// Our sink-hole returns a false_type.
return std::false_type{};
}
public:
// A public operator() that accept the argument we wish to test onto the UnnamedType.
// Notice that the return type is automatic!
template <typename Param> constexpr auto operator()(Param&&)
{
// The argument is forwarded to one of the two overloads.
// The SFINAE on the 'true_type' will come into play to dispatch.
// Once again, we use the int for the precedence.
return testValidity<Param>(int());
}
};
auto l5 = [](auto&& t) -> decltype(std::forward<decltype(t)>(t).serialize()) { };
auto hasSerialize = Container<decltype(l5)>{};
template <class T> auto serialize(T&& obj)
-> typename std::enable_if<decltype(hasSerialize(std::forward<T>(obj)))::value, std::string>::type
{
return std::forward<T>(obj).serialize();
}
template <class T> auto serialize(T&& obj)
-> typename std::enable_if<!decltype(hasSerialize(std::forward<T>(obj)))::value, std::string>::type
{
return to_string(obj);
}
有点复杂,因为我尝试用完美转发来处理右值参数。
然后我需要一个类来测试:
class C {
public:
std::string serialize() && { return "This is C"; } //NOTE: there is a ref-qualifier
};
std::string to_string(const C&) { return "This is C in to_string()"; }
int main() {
C c{};
std::cout << serialize(c) << std::endl;
}
当我在 MacOS (M1) 上用 clang 13.0.0 编译时:
clang++ -std=c++14 -O0
没有编译错误,输出是:“This is C in to_string()”。
那我的问题来了: 在解析
serialze(c)
中的main
调用时,编译器需要对serialize()
函数进行subsitite。然后需要为 hasSerialize::operator()
实例化,因为它出现在 std::enable_if
中(在两个 serialize()
中)。
为了实例化
hasSerialize::operator()
,编译器需要为testValidity<Param>
实例化,其中Param
是C&
,因为它出现在函数体中。
然后编译器会尝试替代
template <typename Param> constexpr auto testValidity(int /* unused */)
-> decltype(std::declval<UnnamedType>()(std::declval<Param>()), std::true_type{})`
最后,编译器将尝试替换通用 lambda
[](auto&& t) -> decltype(std::forward<decltype(t)>(t).serialize()) { };
因为它出现在
decltype
的返回类型 testValidity
中。
替换失败发生在通用lambda处,因为
t
的类型是C&
,但.serialize()
只对C&&
有效。这里的替换失败不在
直接上下文(如果我正确理解这里的答案)。
为什么编译器不抛出硬错误?