无效和非无效方法之间的 SFINAE 调度
SFINAE dispatch between void and non-void method
我有如下内容:
template <typename T>
struct Base {
auto func() {
// do stuff
auto x = static_cast<T&>(*this).func_impl();
// do stuff
return x;
}
};
struct A : Base<A> {
int func_impl() {
return 0;
}
};
struct B : Base<B> {
void func_impl() {
}
};
int main() {
A a;
int i = a.func();
B b;
b.func();
return 0;
}
问题是我无法在派生的 class 到 void
中声明 return 类型 func_impl
,如 B
所示。我尝试像这样使用 SFINAE 解决问题:
template <typename T>
struct Base {
template <typename = enable_if_t<!is_void<decltype(declval<T>().func_impl())>::value>>
auto func() {
// do stuff
auto x = static_cast<T&>(*this).func_impl();
// do stuff
return x;
}
template <typename = enable_if_t<is_void<decltype(declval<T>().func_impl())>::value>>
void func() {
// do stuff
static_cast<T&>(*this).func_impl();
// do stuff
}
};
但是编译器报错:invalid use of incomplete type 'struct A'
and invalid use of incomplete type 'struct B'
。
有没有办法实现我想要的?
试试
template <typename T>
struct Base {
template <typename U = T, typename = enable_if_t<!is_void<decltype(declval<U>().func_impl())>::value>>
auto func() {
// do stuff
return static_cast<T&>(*this).func_impl();
}
template <typename U = T, typename = enable_if_t<is_void<decltype(declval<U>().func_impl())>::value>>
void func() {
// do stuff
static_cast<T&>(*this).func_impl();
}
};
我的意思是... SFINAE 应用于模板;如果你想要 class 中的 enable/disable 方法,它们必须是模板方法(class/struct 是模板 class/struct 的事实不算在内:是 方法 必须是模板。
并且 SFINAE 部分(在本例中为 std::enable_if_t
)必须依赖于方法的模板(在我的示例中为 U
)。
P.s:无论如何,我没有看到返回的问题void
这样的情况:
auto x = static_cast<T&>(*this).func_impl();
// do stuff
return x;
要求 Regular Void 类型。也就是说,这里既然你不需要x
,你只需要return就可以了,你真的需要 func()
到 return void
?我发现通常情况并非如此。人们不应该使用的任何旧的空类型就足够了。因此,让我们轻松编写此案例而不会重复:
namespace details {
struct Void { }; // not intended to be used anywhere
}
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<!std::is_void<R>::value, int> = 0>
R invoke_void(F&& f, Args&&... args) {
return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<std::is_void<R>::value, int> = 0>
details::Void invoke_void(F&& f, Args&&... args) {
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
return details::Void{};
}
此实现使用 C++17 库功能,但可以在 C++14 中实现。这给了我们一个 invoke()
,它把 void
换成 Void
,这样你就可以写:
auto func() {
// do stuff
auto x = invoke_void([](auto& x){ return x.func_impl(); },
static_cast<T&>(*this));
// do stuff
return x;
}
有点罗嗦,但至少我们不必重复 func()
- 一个函数就可以很好地处理这两种情况。
一个不同的替代方案,根据您的解释,它可能更简单也可能更复杂,它是重新排序 func()
:
的正文
auto func() {
// do stuff
scope_exit{
// do stuff after func_impl is invoked
};
return static_cast<T&>(*this).func_impl();
}
这可以让您获得正确的操作顺序,甚至不需要常规的 void。但是,post-func_impl
逻辑放在它之前 - 这可能会造成混淆。不过好处是这个功能还是可以return void
.
SO 上有许多类似 scope_exit
的实现。
我有如下内容:
template <typename T>
struct Base {
auto func() {
// do stuff
auto x = static_cast<T&>(*this).func_impl();
// do stuff
return x;
}
};
struct A : Base<A> {
int func_impl() {
return 0;
}
};
struct B : Base<B> {
void func_impl() {
}
};
int main() {
A a;
int i = a.func();
B b;
b.func();
return 0;
}
问题是我无法在派生的 class 到 void
中声明 return 类型 func_impl
,如 B
所示。我尝试像这样使用 SFINAE 解决问题:
template <typename T>
struct Base {
template <typename = enable_if_t<!is_void<decltype(declval<T>().func_impl())>::value>>
auto func() {
// do stuff
auto x = static_cast<T&>(*this).func_impl();
// do stuff
return x;
}
template <typename = enable_if_t<is_void<decltype(declval<T>().func_impl())>::value>>
void func() {
// do stuff
static_cast<T&>(*this).func_impl();
// do stuff
}
};
但是编译器报错:invalid use of incomplete type 'struct A'
and invalid use of incomplete type 'struct B'
。
有没有办法实现我想要的?
试试
template <typename T>
struct Base {
template <typename U = T, typename = enable_if_t<!is_void<decltype(declval<U>().func_impl())>::value>>
auto func() {
// do stuff
return static_cast<T&>(*this).func_impl();
}
template <typename U = T, typename = enable_if_t<is_void<decltype(declval<U>().func_impl())>::value>>
void func() {
// do stuff
static_cast<T&>(*this).func_impl();
}
};
我的意思是... SFINAE 应用于模板;如果你想要 class 中的 enable/disable 方法,它们必须是模板方法(class/struct 是模板 class/struct 的事实不算在内:是 方法 必须是模板。
并且 SFINAE 部分(在本例中为 std::enable_if_t
)必须依赖于方法的模板(在我的示例中为 U
)。
P.s:无论如何,我没有看到返回的问题void
这样的情况:
auto x = static_cast<T&>(*this).func_impl();
// do stuff
return x;
要求 Regular Void 类型。也就是说,这里既然你不需要x
,你只需要return就可以了,你真的需要 func()
到 return void
?我发现通常情况并非如此。人们不应该使用的任何旧的空类型就足够了。因此,让我们轻松编写此案例而不会重复:
namespace details {
struct Void { }; // not intended to be used anywhere
}
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<!std::is_void<R>::value, int> = 0>
R invoke_void(F&& f, Args&&... args) {
return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<std::is_void<R>::value, int> = 0>
details::Void invoke_void(F&& f, Args&&... args) {
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
return details::Void{};
}
此实现使用 C++17 库功能,但可以在 C++14 中实现。这给了我们一个 invoke()
,它把 void
换成 Void
,这样你就可以写:
auto func() {
// do stuff
auto x = invoke_void([](auto& x){ return x.func_impl(); },
static_cast<T&>(*this));
// do stuff
return x;
}
有点罗嗦,但至少我们不必重复 func()
- 一个函数就可以很好地处理这两种情况。
一个不同的替代方案,根据您的解释,它可能更简单也可能更复杂,它是重新排序 func()
:
auto func() {
// do stuff
scope_exit{
// do stuff after func_impl is invoked
};
return static_cast<T&>(*this).func_impl();
}
这可以让您获得正确的操作顺序,甚至不需要常规的 void。但是,post-func_impl
逻辑放在它之前 - 这可能会造成混淆。不过好处是这个功能还是可以return void
.
SO 上有许多类似 scope_exit
的实现。