我偶然发现了 为什么模板参数扣除在这里不起作用? 最近可以答案是总结到“这是一个未建立的上下文”。
具体来说,第一个说这是这样的事情,然后将其重定向到标准的“详细信息”,而第二个则引用标准,至少可以说是隐秘的。
有人可以向像我这样的凡人解释一下, 什么时候发生,为什么会发生?
I've stumbled over Why is the template argument deduction not working here? recently and the answers can be summed up to "It's a non-deduced context".
Specifically, the first one says it's such a thing and then redirects to the standard for "details", while the second one quotes the standard, which is cryptic to say the least.
Can someone please explain to mere mortals, like myself, what a non-deduced context is, when does it occur, and why does it occur?
发布评论
评论(2)
扣除是指从给定参数确定模板参数类型的过程。它适用于功能模板,
auto
和其他一些情况(例如部分专业化)。例如,请考虑:现在,如果您说
f(x)
,则在其中声明std :: vector< int> x;
,然后t
是 defuced 作为int
,您将获得专业化f< int> 。
为了扣除工作,要推论的模板参数类型必须出现在可推论的上下文中。在此示例中,
f
的函数参数是这样的可推论上下文。也就是说,函数呼叫表达式中的一个参数使我们能够确定模板参数t
应该是什么,以使呼叫表达式有效。但是,也有非 non dded的上下文,在不可能的情况下。 的左侧显示的模板参数
规范的示例是“
::
上下文。 /em>foo< t> ::类型
。 > t ,如果您调用
g(int {})
通常没有答案,则类模板参数和类成员之间没有关系。 。参数扣除 > to
foo< t>
,例如或其他转换(思考std :: String
和char const *
)。现在假设您有一个免费的功能:如果您调用
binary_function(t,u)
,则扣除可能是模棱两可的,因此失败了。但是,仅推断出一个论点,而不是推断另一种论点,从而允许隐式转换是合理的。现在需要明确的非养活上下文,例如这样:(您可能已经遇到了
std :: min(1U,2L)
。)注意:
std :: type_identity
自从C ++以来都可以使用20。Deduction refers to the process of determining the type of a template parameter from a given argument. It applies to function templates,
auto
, and a few other cases (e.g. partial specialization). For example, consider:Now if you say
f(x)
, where you declaredstd::vector<int> x;
, thenT
is deduced asint
, and you get the specializationf<int>
.In order for deduction to work, the template parameter type that is to be deduced has to appear in a deducible context. In this example, the function parameter of
f
is such a deducible context. That is, an argument in the function call expression allows us to determine what the template parameterT
should be in order for the call expression to be valid.However, there are also non-deduced contexts, where no deduction is possible. The canonical example is "a template parameter that appears to the left of a
::
:In this function template, the
T
in the function parameter list is in a non-deduced context. Thus you cannot sayg(x)
and deduceT
. The reason for this is that there is no "backwards correspondence" between arbitrary types and membersFoo<T>::type
. For example, you could have specializations:If you call
g(double{})
there are two possible answers forT
, and if you callg(int{})
there is no answer. In general, there is no relationship between class template parameters and class members, so you cannot perform any sensible argument deduction.Occasionally it is useful to inhibit argument deduction explicitly. This is for example the case for
std::forward
. Another example is when you have conversions fromFoo<U>
toFoo<T>
, say, or other conversions (thinkstd::string
andchar const *
). Now suppose you have a free function:If you call
binary_function(t, u)
, then the deduction may be ambiguous and thus fail. But it is reasonable to deduce only one argument and not deduce the other, thus permitting implicit conversions. Now an explicitly non-deduced context is needed, for example like this:(You may have experienced such deduction problems with something like
std::min(1U, 2L)
.)Note:
std::type_identity
is available in the standard library since C++20.什么是未建立的环境?
非伪造的上下文是无法从某些构造中推导的模板参数的上下文。
例如:
从默认参数
n * n * n
中推导出非类型模板参数的参数显然是不可能的。编译器不仅必须取下n * n
的平方根(尽管语言没有这种“平方根扣除”),还必须为>提出一个值n * n
稀薄的空气。非循证上下文列表 c ++ 20
在下面的列表中,假设
t
是某些模板类型参数n
是某些非类型模板参数每个标题引用,并在
1。
foo&lt; t&gt; ::类型
无法推断出
t
,因为类型
只是一个别名,例如std :: vector&lt; t&gt; :: size_type
。仅知道size_type
(在这种情况下为std :: size_t
),我们如何才能找出t
?我们不能,因为std :: size_t
中没有包含信息。开发人员遇到此问题的最常见方法是试图从迭代器中推导容器。
2。
electType(n)
exltype
的表达式可以任意复杂,并且exltype
specifier是一种类型。如果n
是一个非类型模板参数,那么我们怎么可能只知道类型(例如猜测123
来自int> int
)的值?3。
foo&lt; 0 + n&gt;
,int(&amp;)[0 + n]
可以从
n
从foo&lt; n&gt;
或从数组绑定中推导出通常是不可能的。从理论上讲,诸如0 + n
之类的简单表达式是可能的,但是对于更复杂的表达式(例如n * n * n
),这很快就失控了。4。
void foo(t x = t {})
如果我们称之为这样的功能,例如
foo()
,则扣除将需要一些循环逻辑。类型t
将从默认参数t {}
中推断出其类型为t
,这是从默认参数中推断出来的。 。
这可能不是那么明显,因此我将提供一个示例:
take(int)
和take(float)
匹配函数参数,因此是否模棱两可,无论t 应推论
void(int)
或void(float)
。6。
std :: prinitizer_list
函数参数C ++标准中的示例很好地证明了这一点:
在所有子弹中,这种限制是最人造的。
{1,2,3}
可以通过考虑std :: prinitizer_list&lt; int&gt;
,但有意决定不进行此扣除。7。
void foo(ts ...,int)
无法推导模板参数包
ts ...
。基本问题是,如果我们称为
foo(0)
这在提供0
作为包的参数或int
之间是模棱两可的。范围。在功能模板中,通过将参数包解释为一个空包来解决这种歧义,从而解决了某些情况,但不是全部:
进一步的注意到
,需要遵循许多规则才能扣除。
不从非养活的上下文中推论只是其中之一。
[temp.deduct.deduct.type] p8 列出一种类型必须具有扣除的形式。
与数组相关的另一个间接规则是:
故意禁用类型扣除
有时会故意禁用扣除额,因为他们希望函数模板的用户明确地提供参数。
可以用
std :: type_identity
在C ++ 20中,或在C ++ 20之前使用用户定义的版本。What is a non-deduced context?
A non-deduced context is a context where template arguments cannot be deduced from some construct.
For example:
It's obviously impossible to deduce the argument for the non-type template parameter
N
from the default argumentN * N
. Not only would the compiler have to take the square root ofN * N
(despite the language having no such "square root deduction"), it would also have to come up with a value forN * N
out of thin air.List of non-deduced contexts c++20
In the following list, assume that
T
is some template type parameterN
is some non-type template parameterEach heading cites and explains one bullet in [temp.deduct.type] p5.
1.
Foo<T>::type
There is no way to deduce
T
becausetype
is just an alias such asstd::vector<T>::size_type
. Knowing only thesize_type
(which isstd::size_t
in this case), how could we possibly figure outT
? We cannot, because the information isn't contained withinstd::size_t
.The most common way that developers run into this problem is by trying to deduce the container from an iterator.
2.
decltype(N)
The expression of
decltype
can be arbitrarily complex, and adecltype
specifier is a type. IfN
is a non-type template parameter, how could we possibly know the value from just the type (e.g. guess123
fromint
)?3.
Foo<0 + N>
,int(&)[0 + N]
It would be possible to deduce
N
from justFoo<N>
or from an array bound, but whenN
only appears as a subexpression, it becomes generally impossible. It is theoretically possible for a simple expression such as0 + N
, but this quickly gets out of hand for more complex expressions such asN * N
.4.
void foo(T x = T{})
If we call such a function like
foo()
, then deduction would require some circular logic. The typeT
would be inferred from the default argumentT{}
, whose type isT
, which is inferred from the default argument, ...Note: this case applies to the motivating example at the start of the answer.
5.
void foo(T* function_pointer)
with overload setsThis may not be so obvious, so I'll provide an example:
Both
take(int)
andtake(float)
match the function parameter, so it's ambiguous whetherT
should deduce tovoid(int)
orvoid(float)
.6.
std::initializer_list
function argumentsThe example in the C++ standard demonstrates this well:
Of all the bullets, this restriction is the most artificial.
{1, 2, 3}
could by consideredstd::initializer_list<int>
, but it was intentionally decided not to make this deduction.7.
void foo(Ts..., int)
The template parameter pack
Ts...
cannot be deduced.The basic issue is that if we called
foo(0)
this is ambiguous between providing0
as an argument to the pack, or to theint
parameter.In function templates, this ambiguity is resolved by interpreting the parameter pack as an empty pack, which resolves some cases, but not all:
Further notes
There numerous rules that need to be followed for deduction to be possible.
Not deducing from a non-deduced context is just one of them.
[temp.deduct.type] p8 lists the forms that a type must have for deduction to be possible.
Another indirect rule related to arrays is this:
Intentionally disabling type deduction
Sometimes developers intentionally disable deduction because they want the user of a function template to provide an argument explicitly.
This can be done with
std::type_identity
in C++20, or with a user-defined version of it prior to C++20.