可变函数重载和 SFINAE - 解决歧义以模拟 "hide by signature"
Variadic function Overloading and SFINAE - Solve ambiguity to simulate "hide by signature"
我想在 C++ 中使用“按签名隐藏”而不是“按名称隐藏”。所以我写了一个宏,它定义了一个可变参数函数,如果存在的话,它将所有调用委托给它的基 class 。
我不能使用 using 声明,因为如果基 class 没有具有该名称的方法,我不希望它失败 - 并且只有在没有直接成员匹配时才应考虑继承的方法。
这在大多数情况下都有效,因为它是由可变参数函数实现的,与非可变参数函数相比,可变参数函数总是更差的候选者。
但是当 child class 也有一个可变函数时我有一个问题 -> 调用变得不明确。
所以我得到以下情况(简化 - 没有 sfinae,宏......):
#include <type_traits>
#include <iostream>
class A{
public:
void Do(){
std::cout << "A::Do()\n";
}
};
class B : public A
{
public:
template<
typename... TX,
typename SomeSFINAE = int
>
void Do(TX...){
std::cout << "B::Do()\n";
}
template<typename... T>
void Do(T...){
A::Do();
}
};
int main(){
B b;
b.Do();
return 0;
}
在 godbolt 上查看。
我想解决这种情况,而不是将其中一种方法设为“dispatcher-method”。有没有办法让一种方法成为解决这种歧义的“更差的候选者”?
更新
好像不太清楚我到底想达到什么目的。所以这里有一些带有评论的“pseudo-code”:
#include <type_traits>
#include <iostream>
class A{
public:
void Do(){
std::cout << "A::Do()\n";
}
};
class B : public A
{
public:
template<
typename... TX
>
void Do(TX...){
std::cout << "B::Do()\n";
}
using A::Do; //<--- This should be considered only if no direct match is found in B
//Variadic function should win, because it is defined in B not in A - it should hide A.Do
//It should even work if A has NO method Do
};
int main(){
B b{};
b.Do(); //-> B::Do should be called, not A::Do
return 0;
}
更新
我想从你那里得到类似的东西,你如何使普通函数成为可变参数函数的更差候选者。
例如:
#include <iostream>
void Do(int a){
std::cout << "better";
}
template<typename... T>
void Do(int a, T...){
//this is worse
std::cout << "worse";
}
int main(){
Do(42);
return 0;
}
有什么东西可以使可变参数函数变得更糟吗?
背景:
目前我有以下宏,只是为了像我想要的那样模拟使用。
#define NATIVE_DO_NOT_HIDE_INHERITED_(AMETHOD, ...) \
private: template<typename $T, typename... $Args> \
using CallHiding$ ## AMETHOD = decltype(::std::declval<$T*>()->AMETHOD (::std::declval<$Args>()...)); \
\
public: template< \
typename... $Args \
, typename $Dependent = __VA_ARGS__ \
, bool $Detected = ::CORE_NATIVE_NS ::is_detected_v<CallHiding$ ## AMETHOD, $Dependent, $Args...> \
, typename = typename ::std::enable_if_t<$Detected > \
> \
constexpr decltype(auto) AMETHOD ($Args&&... args) \
{ \
/*allow virtual call*/ \
return static_cast<$Dependent*>(this) -> AMETHOD (::std::forward<$Args>(args)...); \
} \
\
private: template<typename $T, typename $FktArgsTuple, typename $ValueArgsTuple> \
class CallHidingGeneric$ ## AMETHOD : public ::std::bool_constant<false> {\
};\
\
private: template<typename $T, typename... $FktArgs, typename... $ValueArgs> \
class CallHidingGeneric$ ## AMETHOD<$T, ::std::tuple<$FktArgs...>, ::std::tuple<$ValueArgs...>> \
{\
template<typename AType> \
using ATemplate = decltype(::std::declval<AType>().template AMETHOD <$FktArgs...> (::std::declval<$ValueArgs>()...)); \
public: \
constexpr static bool value = ::CORE_NATIVE_NS ::is_detected_v<ATemplate, $T> ; \
}; \
\
public: template< \
typename... $FktArgs \
, typename... $Args \
, typename $Dependent = __VA_ARGS__ \
, typename = ::std::enable_if_t<(sizeof...($FktArgs) > 0)> \
, typename = ::std::enable_if_t< \
CallHidingGeneric$ ## AMETHOD<$Dependent, typename ::std::template tuple<$FktArgs...>, typename ::std::template tuple<$Args...>>::value \
> \
> \
constexpr decltype(auto) AMETHOD ($Args&&... args) \
{ \
return $Dependent ::template AMETHOD <$FktArgs...> (::std::forward<$Args>(args)...); \
}
#define NATIVE_DO_NOT_HIDE_INHERITED(AMETHOD) NATIVE_DO_NOT_HIDE_INHERITED_(AMETHOD, $Next)
#define NATIVE_DO_NOT_HIDE_INHERITED2(AMETHOD, ...) NATIVE_DO_NOT_HIDE_INHERITED_(AMETHOD, typename ::CORE_NATIVE_NS::type_container_t< __VA_ARGS__ >:: $Next)
它适用于“普通”功能 - 但“宏生成的功能”并不被认为更糟糕...
既然你标记了这个 C++20,你可以使用 requires-clause 来约束 B::Do
在 B::Do
或 A::Do
上被调用,然后使用 if constexpr
在正文中:
class B : public A
{
public:
template <typename... TX>
void Do(TX... ts)
requires true || requires (A a) { a.Do(ts...); }
{
if constexpr (true) {
std::cout << "B::Do()\n";
} else {
A::Do(ts...);
}
}
};
这里我使用 true
代替 B::Do
的可调用条件,因此只需在两个地方适当地替换该条件即可。
您可以通过将实际 B::Do
实现隐藏在其他函数中来减少重复:
class B : public A
{
template <typename... TX>
void DoImpl(TX... ts) {
std::cout << "B::Do()\n";
}
public:
template <typename... TX>
void Do(TX... ts)
requires requires (B b) { b.DoImpl(ts...); }
|| requires (A a) { a.Do(ts...); }
{
if constexpr (requires (B b) { b.DoImpl(ts...); }) {
B::DoImpl(ts...);
} else {
A::Do(ts...);
}
}
};
现在你只需要约束B::DoImpl
另一种方法仍然是使用类似 Boost.Hof 的 first_of()
适配器(因为这就是您要尝试做的 - 调用一系列函数中的第一个)。这对于成员函数来说有点尴尬,但你可以让它与私有静态成员一起工作:
class B : public A
{
template <typename... TX>
void DoImpl(TX... ts) {
std::cout << "B::Do()\n";
}
static constexpr auto do_impl =
boost::hof::first_of(
[](B& b, auto... args) BOOST_HOF_RETURNS(b.DoImpl(args...)),
[](A& a, auto... args) BOOST_HOF_RETURNS(a.Do(args...)));
public:
template <typename... TX>
void Do(TX... ts)
requires requires { do_impl(*this, ts...); }
{
return do_impl(*this, ts...);
}
};
我想在 C++ 中使用“按签名隐藏”而不是“按名称隐藏”。所以我写了一个宏,它定义了一个可变参数函数,如果存在的话,它将所有调用委托给它的基 class 。 我不能使用 using 声明,因为如果基 class 没有具有该名称的方法,我不希望它失败 - 并且只有在没有直接成员匹配时才应考虑继承的方法。 这在大多数情况下都有效,因为它是由可变参数函数实现的,与非可变参数函数相比,可变参数函数总是更差的候选者。 但是当 child class 也有一个可变函数时我有一个问题 -> 调用变得不明确。
所以我得到以下情况(简化 - 没有 sfinae,宏......):
#include <type_traits>
#include <iostream>
class A{
public:
void Do(){
std::cout << "A::Do()\n";
}
};
class B : public A
{
public:
template<
typename... TX,
typename SomeSFINAE = int
>
void Do(TX...){
std::cout << "B::Do()\n";
}
template<typename... T>
void Do(T...){
A::Do();
}
};
int main(){
B b;
b.Do();
return 0;
}
在 godbolt 上查看。
我想解决这种情况,而不是将其中一种方法设为“dispatcher-method”。有没有办法让一种方法成为解决这种歧义的“更差的候选者”?
更新
好像不太清楚我到底想达到什么目的。所以这里有一些带有评论的“pseudo-code”:
#include <type_traits>
#include <iostream>
class A{
public:
void Do(){
std::cout << "A::Do()\n";
}
};
class B : public A
{
public:
template<
typename... TX
>
void Do(TX...){
std::cout << "B::Do()\n";
}
using A::Do; //<--- This should be considered only if no direct match is found in B
//Variadic function should win, because it is defined in B not in A - it should hide A.Do
//It should even work if A has NO method Do
};
int main(){
B b{};
b.Do(); //-> B::Do should be called, not A::Do
return 0;
}
更新
我想从你那里得到类似的东西,你如何使普通函数成为可变参数函数的更差候选者。
例如:
#include <iostream>
void Do(int a){
std::cout << "better";
}
template<typename... T>
void Do(int a, T...){
//this is worse
std::cout << "worse";
}
int main(){
Do(42);
return 0;
}
有什么东西可以使可变参数函数变得更糟吗?
背景: 目前我有以下宏,只是为了像我想要的那样模拟使用。
#define NATIVE_DO_NOT_HIDE_INHERITED_(AMETHOD, ...) \
private: template<typename $T, typename... $Args> \
using CallHiding$ ## AMETHOD = decltype(::std::declval<$T*>()->AMETHOD (::std::declval<$Args>()...)); \
\
public: template< \
typename... $Args \
, typename $Dependent = __VA_ARGS__ \
, bool $Detected = ::CORE_NATIVE_NS ::is_detected_v<CallHiding$ ## AMETHOD, $Dependent, $Args...> \
, typename = typename ::std::enable_if_t<$Detected > \
> \
constexpr decltype(auto) AMETHOD ($Args&&... args) \
{ \
/*allow virtual call*/ \
return static_cast<$Dependent*>(this) -> AMETHOD (::std::forward<$Args>(args)...); \
} \
\
private: template<typename $T, typename $FktArgsTuple, typename $ValueArgsTuple> \
class CallHidingGeneric$ ## AMETHOD : public ::std::bool_constant<false> {\
};\
\
private: template<typename $T, typename... $FktArgs, typename... $ValueArgs> \
class CallHidingGeneric$ ## AMETHOD<$T, ::std::tuple<$FktArgs...>, ::std::tuple<$ValueArgs...>> \
{\
template<typename AType> \
using ATemplate = decltype(::std::declval<AType>().template AMETHOD <$FktArgs...> (::std::declval<$ValueArgs>()...)); \
public: \
constexpr static bool value = ::CORE_NATIVE_NS ::is_detected_v<ATemplate, $T> ; \
}; \
\
public: template< \
typename... $FktArgs \
, typename... $Args \
, typename $Dependent = __VA_ARGS__ \
, typename = ::std::enable_if_t<(sizeof...($FktArgs) > 0)> \
, typename = ::std::enable_if_t< \
CallHidingGeneric$ ## AMETHOD<$Dependent, typename ::std::template tuple<$FktArgs...>, typename ::std::template tuple<$Args...>>::value \
> \
> \
constexpr decltype(auto) AMETHOD ($Args&&... args) \
{ \
return $Dependent ::template AMETHOD <$FktArgs...> (::std::forward<$Args>(args)...); \
}
#define NATIVE_DO_NOT_HIDE_INHERITED(AMETHOD) NATIVE_DO_NOT_HIDE_INHERITED_(AMETHOD, $Next)
#define NATIVE_DO_NOT_HIDE_INHERITED2(AMETHOD, ...) NATIVE_DO_NOT_HIDE_INHERITED_(AMETHOD, typename ::CORE_NATIVE_NS::type_container_t< __VA_ARGS__ >:: $Next)
它适用于“普通”功能 - 但“宏生成的功能”并不被认为更糟糕...
既然你标记了这个 C++20,你可以使用 requires-clause 来约束 B::Do
在 B::Do
或 A::Do
上被调用,然后使用 if constexpr
在正文中:
class B : public A
{
public:
template <typename... TX>
void Do(TX... ts)
requires true || requires (A a) { a.Do(ts...); }
{
if constexpr (true) {
std::cout << "B::Do()\n";
} else {
A::Do(ts...);
}
}
};
这里我使用 true
代替 B::Do
的可调用条件,因此只需在两个地方适当地替换该条件即可。
您可以通过将实际 B::Do
实现隐藏在其他函数中来减少重复:
class B : public A
{
template <typename... TX>
void DoImpl(TX... ts) {
std::cout << "B::Do()\n";
}
public:
template <typename... TX>
void Do(TX... ts)
requires requires (B b) { b.DoImpl(ts...); }
|| requires (A a) { a.Do(ts...); }
{
if constexpr (requires (B b) { b.DoImpl(ts...); }) {
B::DoImpl(ts...);
} else {
A::Do(ts...);
}
}
};
现在你只需要约束B::DoImpl
另一种方法仍然是使用类似 Boost.Hof 的 first_of()
适配器(因为这就是您要尝试做的 - 调用一系列函数中的第一个)。这对于成员函数来说有点尴尬,但你可以让它与私有静态成员一起工作:
class B : public A
{
template <typename... TX>
void DoImpl(TX... ts) {
std::cout << "B::Do()\n";
}
static constexpr auto do_impl =
boost::hof::first_of(
[](B& b, auto... args) BOOST_HOF_RETURNS(b.DoImpl(args...)),
[](A& a, auto... args) BOOST_HOF_RETURNS(a.Do(args...)));
public:
template <typename... TX>
void Do(TX... ts)
requires requires { do_impl(*this, ts...); }
{
return do_impl(*this, ts...);
}
};