2017-10-11 42 views
3

考虑下面的代码错误模板实例超载

#include <type_traits> 
#include <utility> 

template <typename T> 
class Something { 
public: 
    template <typename F> 
    auto foo(F&&) 
     -> decltype(std::declval<F>()(std::declval<T&>())) {} 
    template <typename F> 
    auto foo(F&&) const 
     -> decltype(std::declval<F>()(std::declval<const T&>())) {} 
}; 

int main() { 
    auto something = Something<int>{}; 
    something.foo([](auto& val) { 
     ++val; 
    }); 
} 

https://wandbox.org/permlink/j24Pe9qOXV0oHcA8

当我尝试编译此我得到的错误,说不准我在修改的拉姆达一个const值之前主要。这意味着模板在某种程度上都是在类中实例化的,并且由于错误出现在lambda体中,所以导致了一个硬性错误。

这是什么规则?为什么重载决议尝试实例化一个永远不会被调用的模板? const函数不应该在这里被调用,为什么它会试图完全实例化它?

然而,奇怪的是,当我通过decltype(auto)更改定义并返回并添加代码以执行与尾随返回类型相同的操作时,我看不到错误。表明模板没有完全实例化?

template <typename F> 
decltype(auto) foo(F&& f) { 
    auto t = T{}; 
    f(t); 
} 
template <typename F> 
decltype(auto) foo(F&& f) const { 
    const auto t = T{}; 
    f(t); 
} 

我猜编译器不知道在使用传递函数实例化至少签名之前要调用哪个函数。但是,这并不能解释为什么decltype(自动)版本的作品...

回答

4

(道歉缺乏正确的标准术语,它的工作...)

当被调用something.foo,所有可能的过载必须被考虑:

template <typename F> 
auto foo(F&&) 
    -> decltype(std::declval<F>()(std::declval<T&>())) {} 

template <typename F> 
auto foo(F&&) const 
    -> decltype(std::declval<F>()(std::declval<const T&>())) {} 

为了检查过载是否是可行的,后decltype(...)需要由编译器进行评估。第一个decltype将被评估没有错误,它将评估为void

第二个会导致一个错误,因为你试图用const T&来调用lambda。

由于lambda是无约束的,所以在lambda体的实例化过程中会发生错误。发生这种情况是因为(默认情况下)lambda使用自动返回类型扣减,这需要lambda身体的实例化。

因此,不可行的重载将因此导致编译错误,而不是获取SFINAEd。如果约束拉姆达如下...

something.foo([](auto& val) -> decltype(++val, void()) { 
    ++val; 
}); 

...不会发生错误,因为超载将被视为非可行通过SFINAE。此外,您将能够检测lambda调用是否对特定类型有效(即T支持operator++()?)Something::foo


当你改变你的返回类型decltype(auto),返回类型从函数体推断。

template <typename F> 
decltype(auto) foo(F&& f) { 
    auto t = T{}; 
    f(t); 
} 

template <typename F> 
decltype(auto) foo(F&& f) const { 
    const auto t = T{}; 
    f(t); 
} 

正如你something实例是非const,非const合格超载将在这里拍摄。如果您main定义如下:

int main() { 
    const auto something = Something<int>{}; 
    something.foo([](auto& val) { 
     ++val; 
    }); 
} 

你会得到同样的错误,即使decltype(auto)

+0

“*实例化过程中会出现错误*”而不是何时?替代是自己的一步? –

+1

@RyanHaining:在替换步骤中,'decltype(...)'尾随返回类型实例化lambda的body ...导致错误。 –

+0

这里有什么建议避免这个问题的方法?只需切换到'decltype(auto)'?我希望'foo'函数对于返回类型是SFINAE友好的,并且还有用户代码工作 – Curious

1

实际上,我认为这个问题的要点是,

只有无效的类型和表达式中的函数类型和它的模板参数类型的直接背景可能导致扣失败。 [注意:对类型和表达式的替换可能会导致类效应,例如类模板特化和/或函数模板特化的实例化,隐式定义函数的生成等。这些效应不在 “直接上下文”中可能会导致程序不合格。 - 结束注释]

所以,问题是,如果lambda的实例化在其直接上下文中考虑的返回类型被扣除时触发?

例如,如果拉姆达返回类型作出了明确:

something.foo([](auto& val) -> void { 
    ++val; 
}); 

代码编译(无SFINAE,它只是非const为最佳匹配)。

但是,OP的lambda具有自动返回类型演绎,因此lambda被实例化并应用上述规则。

+0

这是有道理的,但请注意,lambda不再是“SFINAE友好”的。您可能想要检查'++ val'是否是'Something :: foo'的'val'的有效操作。如果你只是说' - > void',它可能看起来像一个有效的操作,即使它不是,产生了一个像OP中例子那样的硬错误。 –

+0

@VittorioRomeo ...呃,但它并没有给OP代码带来一个硬错误,这是关键...... –

+0

这是我发现的问题:https://wandbox.org/permlink/g7fNJgknnVXj7vrt - 我会希望在那里选择'/ * fallback * /',但' - > void'选择另一个重载并且**导致硬错误**。正确地约束lambda将选择回退。 –