在使用通用引用的同时,我遇到了clang和gcc在重载决策上不一致的情况.
#include <iostream>
struct foo {};
template<typename T>
void bar(T&) { std::cout << "void bar(T&)\n"; }
template<typename T>
void bar(T&&) { std::cout << "void bar(T&&)\n"; }
int main()
{
foo f;
bar(f); // ambiguous on gcc, ok on clang
}
Run Code Online (Sandbox Code Playgroud)
gcc报告上面的调用是模棱两可的.但是,clang选择T&重载并成功编译.
哪个编译器有问题,为什么?
编辑:
在VS2013预览中测试相同的代码,它同意clang; 除了Intellisense,这是在gcc的一方:-)
c++ overload-resolution c++11 universal-reference forwarding-reference
GCC(使用4.9测试)接受以下测试用例:
struct Base {};
struct Derived : Base {
Derived();
explicit Derived(const Derived&);
explicit Derived(Derived&&);
explicit Derived(const Base&);
Derived(Base&&);
};
Derived foo() {
Derived result;
return result;
}
int main() {
Derived result = foo();
}
Run Code Online (Sandbox Code Playgroud)
Clang(使用3.5测试)拒绝它,并显示以下错误消息:
test.cpp:13:10: error: no matching constructor for initialization of 'Derived'
return result;
^~~~~~
test.cpp:8:5: note: candidate constructor not viable: no known conversion from 'Derived' to 'Base &&' for 1st argument
Derived(Base&&);
^
test.cpp:4:5: note: candidate constructor not viable: requires 0 arguments, but 1 was …Run Code Online (Sandbox Code Playgroud) 当我发现下面的代码输出"指针"时,我遇到了真实的WTF时刻.
#include <iostream>
#include <utility>
template<typename T>
struct bla
{
static void f(const T*) { std::cout << "pointer\n"; }
static void f(const T&) { std::cout << "reference\n"; }
};
int main()
{
bla<std::pair<int,int>>::f({});
}
Run Code Online (Sandbox Code Playgroud)
将std::pair<int,int>模板参数更改为int或任何其他基本类型,给(至少对我来说)预期的"模糊过载"错误.似乎内置类型在这里是特殊的,因为任何用户定义的类型(聚合,非平凡,具有默认构造函数等)都会导致调用指针超载.我相信模板不是重现它的必要条件,它只是让尝试不同类型变得简单.
就个人而言,我不认为这是合乎逻辑的,我认为在所有情况下都会出现模糊的重载错误,无论模板参数如何.GCC和Clang(我相信MSVC)在C++ 11/14/1z中都不同意我的观点.注意我完全知道这两个重载存在的错误API ,我保证不会写这样的东西.
所以问题就变成了:发生了什么?
与我的期望相反,这个程序有效:
#include <iostream>
namespace a { struct item{}; }
namespace b { struct item{}; }
template<typename T>
void func(T t) { do_func(t); }
int main()
{
func(a::item{});
func(b::item{});
}
namespace a { void do_func(item) { std::cout << "a::func\n"; } }
namespace b { void do_func(item) { std::cout << "b::func\n"; } }
Run Code Online (Sandbox Code Playgroud)
输出:
a::func
b::func
Run Code Online (Sandbox Code Playgroud)
在线编译器验证:
如果瞬间func<T>发生在身体main然后我会期待a::do_func并且b::do_func尚未宣布.
这怎么办?
根据@Marc Claesen的说法,上述原因是:
在读取所有源之后执行模板实例化
但是,那么为什么这个代码并不能正常工作:
#include <iostream>
template<typename T>
void …Run Code Online (Sandbox Code Playgroud) 理解关于重载解析的C#语言规范显然很难,现在我想知道为什么这个简单的情况失败了:
void Method(Func<string> f)
{
}
void Method(Func<object> f)
{
}
void Call()
{
Method(() => { throw new NotSupportedException(); });
}
Run Code Online (Sandbox Code Playgroud)
这给出了编译时错误CS0121,以下方法或属性之间的调用是不明确的:后跟我的两个Method函数成员(重载).
我本来期望的是,Func<string>是一个更好的转换目标比Func<object>,然后应使用第一个重载.
由于.NET 4和C#4(2010),通用委托类型Func<out TResult>已协变中TResult,并且由于该原因的隐式转换从存在Func<string>于Func<object>同时明确的隐式转换可以从存在Func<object>于Func<string>.那么它会产生Func<string>更好的转换目标,而重载分辨率应该选择第一个过载?
我的问题很简单:我在这里错过了C#规范的哪一部分?
增加:这很好用:
void Call()
{
Method(null); // OK!
}
Run Code Online (Sandbox Code Playgroud) 使用clang 3.7.1,C++ 14可以很好地编译具有可变参数模板的SFINAE代码:
#include <array>
#include <iostream>
#include <vector>
#include <cstdint>
enum class Bar : uint8_t {
ay, bee, see
};
struct S {
static void foo() {}
// std::begin(h) is defined for h of type H
template<typename H, typename... T>
static typename std::enable_if<std::is_pointer<decltype(std::begin(std::declval<H>()))*>::value>::type
foo(const H&, T&&... t)
{ std::cout << "container\n"; foo(std::forward<T>(t)...); }
// H is integral
template<typename H, typename... T>
static typename std::enable_if<std::is_integral<typename std::remove_reference<H>::type>::value>::type
foo(const H&, T&&... t)
{ std::cout << "integer\n"; foo(std::forward<T>(t)...); }
// H is an …Run Code Online (Sandbox Code Playgroud) 请考虑以下代码:
template <typename T>
struct dependent_type
{
using type = T;
};
template <typename T>
auto foo(T) -> std::enable_if_t<std::is_same<T, int>{}>
{
std::cout << "a\n";
}
template<typename T>
void foo(typename dependent_type<T>::type)
{
std::cout << "b\n";
}
Run Code Online (Sandbox Code Playgroud)
第一个重载foo可以T从其调用中推断出来.
的第二过载foo是一个非推测的上下文.
int main()
{
foo<int>( 1 ); // prints "b"
foo<double>( 1.0 ); // prints "b"
foo( 1 ); // prints "a"
}
Run Code Online (Sandbox Code Playgroud)
为什么foo<int>( 1 )打印"b"而不是"a"?
test对于左值空字符串,左值非空字符串和右值字符串,下面的函数已重载。我尝试使用Clang和GCC进行编译,但在两种情况下都没有达到我期望的结果。
#include <iostream>
void test(const char (&)[1]){ std::cout << __PRETTY_FUNCTION__ << std::endl; }
template <unsigned long int N>
void test(const char (&)[N]){ std::cout << __PRETTY_FUNCTION__ << std::endl; }
void test(char*&&){ std::cout << __PRETTY_FUNCTION__ << std::endl; }
int main(){
char str1[] = "";
char str2[] = "test";
test("");
test("test");
test(str1);
test(str2);
}
Run Code Online (Sandbox Code Playgroud)
使用clang 版本6.0.0-1ubuntu2的输出:
#include <iostream>
void test(const char (&)[1]){ std::cout << __PRETTY_FUNCTION__ << std::endl; }
template <unsigned long int N>
void test(const char (&)[N]){ std::cout << __PRETTY_FUNCTION__ << …Run Code Online (Sandbox Code Playgroud) 众所周知,仅返回类型不同的普通函数不能在 C++ 中重载。
但这个限制不适用于重载的函数模板,例如:
int f(auto) { return 1; }
auto f(auto) { return 2; }
Run Code Online (Sandbox Code Playgroud)
所有编译器都接受它,演示: https: //gcc.godbolt.org/z/qj73Mzehd
为什么该语言对模板做出这样的例外?
如果重载函数的返回类型不同,则可以使用强制转换为预期函数类型来选择其中一个函数。令人惊讶的是,即使返回类型实际上相同,Clang 也允许解决歧义,例如:
((int(*)(int))f)(3);
Run Code Online (Sandbox Code Playgroud)
选择
int f(auto) { return 1; }
Run Code Online (Sandbox Code Playgroud)
演示: https: //gcc.godbolt.org/z/snfvbq1ME
Clang这里错了吗?