声明 const 时模板成员函数解析失败
template member function resolution fails when declaring const
下面的代码显示了一些有趣的行为:
#include <iostream>
using namespace std;
template<class T>
class B{
public:
void foo(B<T> &x)const;
template<class F> void foo(F f);
};
template<typename T> void B<T>::foo(B<T> &x)const{cout<<"foo_B"<<endl;}
template<typename T> template<typename F> void B<T>::foo(F f){cout<<"foo_F"<<endl;}
int main(){
B<int> a;
B<int> b;
b.foo(a);
b.foo([](){;});
return(0);
}
我的预期输出是
foo_B
foo_F
但实际输出是
foo_F
foo_F
这取决于void foo(B<T> &x)
是否声明const
。如果省略 const
,则输出符合预期。
此外,如果将 const
添加到 void foo(F f)
,输出也符合预期。
但是,void foo(B<T> &x)
不会更改 this
,而 void foo(F f)
会更改 this
。所以当前的布局是必需的。
非常感谢知道如何在不删除 const
的情况下解决此问题。
资格转换(此处:在隐式对象参数上)不是身份转换,并且在重载解析排名中有成本
成员函数void foo(B<T>& x) const
是const
限定的,而模板成员函数template<class F> void foo(F f)
不是。这意味着后者更适合隐式对象参数为 not const 的调用,根据 [over.ics.rank]/3.2.5:
Standard conversion sequence S1 is a better conversion sequence than
standard conversion sequence S2 if
- [...], or, if not that, S1 and S2 differ only in their qualification conversion ([conv.qual]) and yield similar types T1 and T2,
respectively, where T1 can be converted to T2 by a qualification
conversion.
如果在main中const
限定自动变量b
,将选择非模板重载:
// ^^^ `foo` overloads as in OP's example
B<int> a{};
B<int> const b{}
b.foo(a); // foo_B
如果您改为 const
-quality 模板成员函数 foo
,则非模板将与模板重载相同匹配(隐式对象参数需要 const 限定),在这种情况下,根据 [over.match.best]/2.4.
选择非模板函数作为最佳可行重载
如果您不想让特定重载参与 类型谓词 的重载决议:删除它
However, b cannot be declared const in the actual application, which boil down to making a copy, say const c(b); and then use c.foo(a)
当其类型模板参数的模板实参是 B
class 模板的特化时,您可以使用特征删除模板成员函数:
#include <iostream>
#include <type_traits>
template <class T, template <class...> class Primary>
struct is_specialization_of : std::false_type {};
template <template <class...> class Primary, class... Args>
struct is_specialization_of<Primary<Args...>, Primary> : std::true_type {};
template <class T, template <class...> class Primary>
inline constexpr bool is_specialization_of_v{is_specialization_of<T, Primary>::value};
template <class T> class B {
public:
void foo(B<T> &x) const { std::cout << "foo_B" << std::endl; }
template <class F, typename = std::enable_if_t<!is_specialization_of_v<F, B>>>
void foo(F f) {
std::cout << "foo_F" << std::endl;
}
};
int main() {
B<int> a;
B<int> b;
b.foo(a);
b.foo([]() { ; });
return (0);
}
我们利用了 P2098R1 的 is_specialization_of
特性(请注意,这对于作为别名模板的模板参数有实现差异 - IIRC 有点不明确)。
请注意,使用这种方法,重载的 none 对于 另一个 特化 B
的参数(而不是隐式对象参数).
这里的问题是,由于 void foo(B<T> &x)const;
是 const 限定的,它必须对您调用函数的对象进行 const 限定。这与 template<class F> void foo(F f);
提供的匹配不完全相同,因为它不需要进行 const 限定。这就是它用于两个调用的原因。
您也可以通过 const 限定模板版本来解决此问题,例如:
#include <iostream>
using namespace std;
template<class T>
class B{
public:
void foo(B<T> &x)const;
template<class F> void foo(F f)const;
};
template<typename T> void B<T>::foo(B<T> &x)const{cout<<"foo_B"<<endl;}
template<typename T> template<typename F> void B<T>::foo(F f)const{cout<<"foo_F"<<endl;}
int main(){
B<int> a;
B<int> b;
b.foo(a);
b.foo([](){;});
return(0);
}
哪个会打印
foo_B
foo_F
另一种选择是使用 SFINAE 来限制模板版本排除 B<T>
。那看起来像
#include <iostream>
using namespace std;
template<class T>
class B{
public:
void foo(B<T> &x)const;
template<class F, std::enable_if_t<!std::is_same_v<B<T>, F>, bool> = true>
void foo(F f);
};
template<typename T> void B<T>::foo(B<T> &x)const{cout<<"foo_B"<<endl;}
template<typename T> template<class F, std::enable_if_t<!std::is_same_v<B<T>, F>, bool>>
void B<T>::foo(F f){cout<<"foo_F"<<endl;}
int main(){
B<int> a;
B<int> b;
b.foo(a);
b.foo([](){;});
return(0);
}
并且与第一个示例具有相同的输出。
一个相当简单但有点冗长的方法是在你想调用 const 版本时将对象转换为 const:
static_cast<const B<int>>(b).foo(a);
这足以让调用显示 foo_B
...
下面的代码显示了一些有趣的行为:
#include <iostream>
using namespace std;
template<class T>
class B{
public:
void foo(B<T> &x)const;
template<class F> void foo(F f);
};
template<typename T> void B<T>::foo(B<T> &x)const{cout<<"foo_B"<<endl;}
template<typename T> template<typename F> void B<T>::foo(F f){cout<<"foo_F"<<endl;}
int main(){
B<int> a;
B<int> b;
b.foo(a);
b.foo([](){;});
return(0);
}
我的预期输出是
foo_B
foo_F
但实际输出是
foo_F
foo_F
这取决于void foo(B<T> &x)
是否声明const
。如果省略 const
,则输出符合预期。
此外,如果将 const
添加到 void foo(F f)
,输出也符合预期。
但是,void foo(B<T> &x)
不会更改 this
,而 void foo(F f)
会更改 this
。所以当前的布局是必需的。
非常感谢知道如何在不删除 const
的情况下解决此问题。
资格转换(此处:在隐式对象参数上)不是身份转换,并且在重载解析排名中有成本
成员函数void foo(B<T>& x) const
是const
限定的,而模板成员函数template<class F> void foo(F f)
不是。这意味着后者更适合隐式对象参数为 not const 的调用,根据 [over.ics.rank]/3.2.5:
Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if
- [...], or, if not that, S1 and S2 differ only in their qualification conversion ([conv.qual]) and yield similar types T1 and T2, respectively, where T1 can be converted to T2 by a qualification conversion.
如果在main中const
限定自动变量b
,将选择非模板重载:
// ^^^ `foo` overloads as in OP's example
B<int> a{};
B<int> const b{}
b.foo(a); // foo_B
如果您改为 const
-quality 模板成员函数 foo
,则非模板将与模板重载相同匹配(隐式对象参数需要 const 限定),在这种情况下,根据 [over.match.best]/2.4.
如果您不想让特定重载参与 类型谓词 的重载决议:删除它
However, b cannot be declared const in the actual application, which boil down to making a copy, say const c(b); and then use c.foo(a)
当其类型模板参数的模板实参是 B
class 模板的特化时,您可以使用特征删除模板成员函数:
#include <iostream>
#include <type_traits>
template <class T, template <class...> class Primary>
struct is_specialization_of : std::false_type {};
template <template <class...> class Primary, class... Args>
struct is_specialization_of<Primary<Args...>, Primary> : std::true_type {};
template <class T, template <class...> class Primary>
inline constexpr bool is_specialization_of_v{is_specialization_of<T, Primary>::value};
template <class T> class B {
public:
void foo(B<T> &x) const { std::cout << "foo_B" << std::endl; }
template <class F, typename = std::enable_if_t<!is_specialization_of_v<F, B>>>
void foo(F f) {
std::cout << "foo_F" << std::endl;
}
};
int main() {
B<int> a;
B<int> b;
b.foo(a);
b.foo([]() { ; });
return (0);
}
我们利用了 P2098R1 的 is_specialization_of
特性(请注意,这对于作为别名模板的模板参数有实现差异 - IIRC 有点不明确)。
请注意,使用这种方法,重载的 none 对于 另一个 特化 B
的参数(而不是隐式对象参数).
这里的问题是,由于 void foo(B<T> &x)const;
是 const 限定的,它必须对您调用函数的对象进行 const 限定。这与 template<class F> void foo(F f);
提供的匹配不完全相同,因为它不需要进行 const 限定。这就是它用于两个调用的原因。
您也可以通过 const 限定模板版本来解决此问题,例如:
#include <iostream>
using namespace std;
template<class T>
class B{
public:
void foo(B<T> &x)const;
template<class F> void foo(F f)const;
};
template<typename T> void B<T>::foo(B<T> &x)const{cout<<"foo_B"<<endl;}
template<typename T> template<typename F> void B<T>::foo(F f)const{cout<<"foo_F"<<endl;}
int main(){
B<int> a;
B<int> b;
b.foo(a);
b.foo([](){;});
return(0);
}
哪个会打印
foo_B
foo_F
另一种选择是使用 SFINAE 来限制模板版本排除 B<T>
。那看起来像
#include <iostream>
using namespace std;
template<class T>
class B{
public:
void foo(B<T> &x)const;
template<class F, std::enable_if_t<!std::is_same_v<B<T>, F>, bool> = true>
void foo(F f);
};
template<typename T> void B<T>::foo(B<T> &x)const{cout<<"foo_B"<<endl;}
template<typename T> template<class F, std::enable_if_t<!std::is_same_v<B<T>, F>, bool>>
void B<T>::foo(F f){cout<<"foo_F"<<endl;}
int main(){
B<int> a;
B<int> b;
b.foo(a);
b.foo([](){;});
return(0);
}
并且与第一个示例具有相同的输出。
一个相当简单但有点冗长的方法是在你想调用 const 版本时将对象转换为 const:
static_cast<const B<int>>(b).foo(a);
这足以让调用显示 foo_B
...