什么是未建立的环境?

发布于 2025-01-22 13:04:37 字数 280 浏览 3 评论 0 原文

我偶然发现了 为什么模板参数扣除在这里不起作用? 最近可以答案是总结到“这是一个未建立的上下文”。

具体来说,第一个说这是这样的事情,然后将其重定向到标准的“详细信息”,而第二个则引用标准,至少可以说是隐秘的。

有人可以向像我这样的凡人解释一下, 什么时候发生,为什么会发生?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

不…忘初心 2025-01-29 13:04:37

扣除是指从给定参数确定模板参数类型的过程。它适用于功能模板, auto 和其他一些情况(例如部分专业化)。例如,请考虑:

template <typename T> void f(std::vector<T>);

现在,如果您说 f(x),则在其中声明 std :: vector&lt; int&gt; x; ,然后 t defuced 作为 int ,您将获得专业化 f&lt; int&gt;

为了扣除工作,要推论的模板参数类型必须出现在可推论的上下文中。在此示例中, f 的函数参数是这样的可推论上下文。也就是说,函数呼叫表达式中的一个参数使我们能够确定模板参数 t 应该是什么,以使呼叫表达式有效。

但是,也有非 non dded的上下文,在不可能的情况下。 的左侧显示的模板参数

template <typename> struct Foo;

template <typename T> void g(typename Foo<T>::type);

规范的示例是“ :: 上下文。 /em> foo&lt; t&gt; ::类型

 template <> struct Foo<int>       { using type = double; };
 template <> struct Foo<char>      { using type = double; };
 template <> struct Foo<float>     { using type = bool; };
 template <> struct Foo<long>      { int type = 10; };
 template <> struct Foo<unsigned>  { };

。 > t ,如果您调用 g(int {})通常没有答案,则类模板参数和类成员之间没有关系。 。


参数扣除 > to foo&lt; t&gt; ,例如或其他转换(思考 std :: String char const *)。现在假设您有一个免费的功能:

template <typename T> bool binary_function(Foo<T> lhs, Foo<T> rhs);

如果您调用 binary_function(t,u),则扣除可能是模棱两可的,因此失败了。但是,仅推断出一个论点,而不是推断另一种论点,从而允许隐式转换是合理的。现在需要明确的非养活上下文,例如这样:(

template <typename T>
struct type_identity {
    using type = T;
};

template <typename T>
bool binary_function(Foo<T> lhs, typename type_identity<Foo<T>>::type rhs)
{
    return binary_function(lhs, rhs);
}

您可能已经遇到了 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:

template <typename T> void f(std::vector<T>);

Now if you say f(x), where you declared std::vector<int> x;, then T is deduced as int, and you get the specialization f<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 parameter T 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 :::

template <typename> struct Foo;

template <typename T> void g(typename Foo<T>::type);

In this function template, the T in the function parameter list is in a non-deduced context. Thus you cannot say g(x) and deduce T. The reason for this is that there is no "backwards correspondence" between arbitrary types and members Foo<T>::type. For example, you could have specializations:

 template <> struct Foo<int>       { using type = double; };
 template <> struct Foo<char>      { using type = double; };
 template <> struct Foo<float>     { using type = bool; };
 template <> struct Foo<long>      { int type = 10; };
 template <> struct Foo<unsigned>  { };

If you call g(double{}) there are two possible answers for T, and if you call g(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 from Foo<U> to Foo<T>, say, or other conversions (think std::string and char const *). Now suppose you have a free function:

template <typename T> bool binary_function(Foo<T> lhs, Foo<T> rhs);

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:

template <typename T>
struct type_identity {
    using type = T;
};

template <typename T>
bool binary_function(Foo<T> lhs, typename type_identity<Foo<T>>::type rhs)
{
    return binary_function(lhs, rhs);
}

(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.

思慕 2025-01-29 13:04:37

什么是未建立的环境?

非伪造的上下文是无法从某些构造中推导的模板参数的上下文。
例如:

template <int N>
void foo(int x = N * N); // called like foo()

从默认参数 n * n * n 中推导出非类型模板参数的参数显然是不可能的。编译器不仅必须取下 n * n 的平方根(尽管语言没有这种“平方根扣除”),还必须为>提出一个值n * n 稀薄的空气。

非循证上下文列表 c ++ 20

在下面的列表中,假设

  • t 是某些模板类型参数
  • n 是某些非类型模板参数
template <typename T, int N>

每个标题引用,并在

1。 foo&lt; t&gt; ::类型

使用A 使用A 。 id.qual#nt:合格 - id“ rel =” nofollow noreferrer“> commified-id


无法推断出 t ,因为类型只是一个别名,例如 std :: vector&lt; t&gt; :: size_type 。仅知道 size_type (在这种情况下为 std :: size_t ),我们如何才能找出 t ?我们不能,因为 std :: size_t 中没有包含信息。

开发人员遇到此问题的最常见方法是试图从迭代器中推导容器。

template <typename T>
void foo(typename T::iterator it); // attempt to deduce T from an iterator

int main() {
    std::array<int> v;
    foo(v.begin()); // error, trying to deduce std::array<int> from int* (probably)
}                   // which is obviously imposssible

2。 electType(n)

expression A


template <int N>
void foo(decltype(N * N - N) 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 ,这是从默认参数中推断出来的。 。


关联参数为重载集的函数参数(


这可能不是那么明显,因此我将提供一个示例:

void take(int);
void take(float);

template <typename T>
void foo(T* function_pointer) { function_pointer(0); }
// note: T(*function_pointer)(int)  would work, and T would deduce to void

int main() { foo(&take); } // error

take(int) take(float)匹配函数参数,因此是否模棱两可,无论 t 应推论 void(int) void(float)

6。 std :: prinitizer_list 函数参数

关联参数为初始化列表的函数参数()但是该参数没有指定从初始化器列表中扣除的类型( [temp.deduct.call] )。

C ++标准中的示例很好地证明了这一点:

  template&lt; class t&gt; void g(t);
g({1,2,3}); //错误:t没有推论
 

在所有子弹中,这种限制是最人造的。 {1,2,3} 可以通过考虑 std :: prinitizer_list&lt; int&gt; ,但有意决定不进行此扣除。

7。 void foo(ts ...,int)

参数 - 删除列表

无法推导模板参数包 ts ...
基本问题是,如果我们称为 foo(0)这在提供 0 作为包的参数或 int 之间是模棱两可的。范围。
在功能模板中,通过将参数包解释为一个空包来解决这种歧义,从而解决了某些情况,但不是全部:

template <typename... Ts>
void foo(Ts..., int);

int main() {
    foo(0);     // OK, Ts... is an empty pack
    foo(0, 0);  // ill-formed despite being intuitively unambiguous
}

进一步的注意到

,需要遵循许多规则才能扣除。
不从非养活的上下文中推论只是其中之一。
[temp.deduct.deduct.type] p8 列出一种类型必须具有扣除的形式。

与数组相关的另一个间接规则是:

template <int N> foo(int(&)[N]); // N can be deduced from array size
template <int N> foo(int[N]);    // N cannot be deduced because arrays in parameters
                                 // are adjusted to pointers

故意禁用类型扣除

有时会故意禁用扣除额,因为他们希望函数模板的用户明确地提供参数。
可以用 std :: type_identity 在C ++ 20中,或在C ++ 20之前使用用户定义的版本。

template <typename T>
void foo(std::type_identity<T>::type); // non-deduced context in function parameter

What is a non-deduced context?

A non-deduced context is a context where template arguments cannot be deduced from some construct.
For example:

template <int N>
void foo(int x = N * N); // called like foo()

It's obviously impossible to deduce the argument for the non-type template parameter N from the default argument N * N. Not only would the compiler have to take the square root of N * N (despite the language having no such "square root deduction"), it would also have to come up with a value for N * N out of thin air.

List of non-deduced contexts

In the following list, assume that

  • T is some template type parameter
  • N is some non-type template parameter
template <typename T, int N>

Each heading cites and explains one bullet in [temp.deduct.type] p5.

1. Foo<T>::type

The nested-name-specifier of a type that was specified using a qualified-id.

There is no way to deduce T because type is just an alias such as std::vector<T>::size_type. Knowing only the size_type (which is std::size_t in this case), how could we possibly figure out T? We cannot, because the information isn't contained within std::size_t.

The most common way that developers run into this problem is by trying to deduce the container from an iterator.

template <typename T>
void foo(typename T::iterator it); // attempt to deduce T from an iterator

int main() {
    std::array<int> v;
    foo(v.begin()); // error, trying to deduce std::array<int> from int* (probably)
}                   // which is obviously imposssible

2. decltype(N)

The expression of a decltype-specifier.

template <int N>
void foo(decltype(N * N - N) n);

The expression of decltype can be arbitrarily complex, and a decltype specifier is a type. If N is a non-type template parameter, how could we possibly know the value from just the type (e.g. guess 123 from int)?

3. Foo<0 + N>, int(&)[0 + N]

A non-type template argument or an array bound in which a subexpression references a template parameter.

It would be possible to deduce N from just Foo<N> or from an array bound, but when N only appears as a subexpression, it becomes generally impossible. It is theoretically possible for a simple expression such as 0 + N, but this quickly gets out of hand for more complex expressions such as N * N.

4. void foo(T x = T{})

A template parameter used in the parameter type of a function parameter that has a default argument that is being used in the call for which argument deduction is being done.

If we call such a function like foo(), then deduction would require some circular logic. The type T would be inferred from the default argument T{}, whose type is T, 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 sets

A function parameter for which the associated argument is an overload set ([over.over]), and one or more of the following apply:

  • more than one function matches the function parameter type (resulting in an ambiguous deduction), or
  • no function matches the function parameter type, or
  • the overload set supplied as an argument contains one or more function templates.

This may not be so obvious, so I'll provide an example:

void take(int);
void take(float);

template <typename T>
void foo(T* function_pointer) { function_pointer(0); }
// note: T(*function_pointer)(int)  would work, and T would deduce to void

int main() { foo(&take); } // error

Both take(int) and take(float) match the function parameter, so it's ambiguous whether T should deduce to void(int) or void(float).

6. std::initializer_list function arguments

A function parameter for which the associated argument is an initializer list ([dcl.init.list]) but the parameter does not have a type for which deduction from an initializer list is specified ([temp.deduct.call]).

The example in the C++ standard demonstrates this well:

template<class T> void g(T);
g({1,2,3});                 // error: no argument deduced for T

Of all the bullets, this restriction is the most artificial. {1, 2, 3} could by considered std::initializer_list<int>, but it was intentionally decided not to make this deduction.

7. void foo(Ts..., int)

A function parameter pack that does not occur at the end of the parameter-declaration-list.

The template parameter pack Ts... cannot be deduced.
The basic issue is that if we called foo(0) this is ambiguous between providing 0 as an argument to the pack, or to the int parameter.
In function templates, this ambiguity is resolved by interpreting the parameter pack as an empty pack, which resolves some cases, but not all:

template <typename... Ts>
void foo(Ts..., int);

int main() {
    foo(0);     // OK, Ts... is an empty pack
    foo(0, 0);  // ill-formed despite being intuitively unambiguous
}

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:

template <int N> foo(int(&)[N]); // N can be deduced from array size
template <int N> foo(int[N]);    // N cannot be deduced because arrays in parameters
                                 // are adjusted to pointers

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.

template <typename T>
void foo(std::type_identity<T>::type); // non-deduced context in function parameter
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文