简体   繁体   English

模板化转换运算符优先级

[英]Templated conversion operator priority

Some C++ hacks use the conversion operator the get some information about the constructor.一些 C++ hacks 使用转换运算符来获取有关构造函数的一些信息。 I would like to know, what is the process when choosing a concrete type for T in the resolution of a templated cast operator.我想知道,在模板化转换运算符的解析中为T选择具体类型的过程是什么。

#include <iostream>
#include <type_traits>

using std::cout;
using std::endl;

struct A {
    A(int) { cout << "int" << endl; }
    A() { cout << "def" << endl; }
    A(const A&) { cout << "copy" << endl; }
    A(A&&) { cout << "move" << endl; }
};

struct B {
    template<typename T> operator T()
        { return {}; }
};

template<typename Except>
struct C {
    template<typename T,
             std::enable_if_t<!std::is_same_v<T, Except>>* = nullptr> operator T()
        { return {}; }
};

template<typename T>
void f(A a = { T() }) {}

int main() {
    f<B>();
    f<C<A>>();

    return 0;
}

This code print this:此代码打印此:

def
int

And not this:而不是这个:

int
int

Why should I disable conversion for taking the constructor I want (int version)?为什么我应该禁用转换以获取我想要的构造函数(int 版本)? The C++ standard says that the return type don't participate in finding a valid template overload, so why it choose this version without complaining about multiple possible resolutions ? C++ 标准说返回类型不参与寻找有效的模板重载,那么为什么它选择这个版本而不抱怨多种可能的解决方案呢?

Makefile:生成文件:

EXE = C++Tuple
CXX = g++
CXXFLAGS = -std=c++17

run: $(EXE)
    ./$(EXE)
.PHONY: run

$(EXE): main.cpp
    $(CXX) $(CXXFLAGS) -o $@ $<

Plateform:平台:

$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 7.5.0-3ubuntu1~18.04' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --program-suffix=-7 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)
$ uname -r
4.4.0-19041-Microsoft

First, several compilers/versions, including current GCC, do reject this as written.首先,包括当前的 GCC 在内的几个编译器/版本确实拒绝了这一点。

Copy initialization复制初始化

Let's start with a simpler case:让我们从一个更简单的案例开始:

template<typename T>
void f(A = T()) {}

Here, we have an expression of type T that we want to (implicitly) convert to A .在这里,我们有一个T类型的表达式,我们想要(隐式)转换为A Unsurprisingly, B produces operator A as a specialization of its conversion function template, and C produces nothing because of SFINAE.不出所料, B产生operator A作为其转换函数模板的特化,而C由于 SFINAE 没有产生任何东西。

Note that the "expected" return type of a conversion function template definitely contributes to template argument deduction (since otherwise it would be impossible to ever deduce anything for them!).请注意,转换函数模板的“预期”返回类型肯定有助于模板参数推导(否则就不可能为它们推导任何东西!)。 Moreover, T is never deduced as A&& or so, even though that would be allowed by the constraint;此外, T永远不会被推导出为A&&左右,即使约束是允许的; only the singular, "obvious" type is used for such deduction, even though certain other types ( eg , a derived class) are allowed for (non-template) conversion functions (for which no effort needs to expended per allowed type).即使某些其他类型(例如,派生类)被允许用于(非模板)转换函数(无需为每个允许的类型花费精力),也仅使用单数的“明显”类型进行此类推导。

Clang produces a confusing error message here, saying that it can't convert C<A> to int in attempting to call the A(int) constructor; Clang 在这里产生了一个令人困惑的错误消息,说它在尝试调用A(int)构造函数时无法将C<A>转换为int that conversion is of course possible in general, but is disallowed in this case by the usual rule about multiple user-defined conversions in [over.best.ics.general]/4:这种转换通常是可能的,但在这种情况下,[over.best.ics.general]/4 中关于多个用户定义的转换的通常规则是不允许的:

However, if the target is the first parameter of a constructor [...] and the constructor or user-defined conversion function is a candidate by [...] , [over.match.copy], or [...] user-defined conversion sequences are not considered.但是,如果目标是构造函数[...]的第一个参数,并且构造函数或用户定义的转换函数是[...] 、 [over.match.copy] 或[...]的候选对象不考虑用户定义的转换序列。

List initialization列表初始化

However, the multiple-conversions rule doesn't apply to list initialization in general (because you perform normal overload resolution, allowing conversions, inside each layer of braces).但是,多次转换规则通常不适用于列表初始化(因为您执行正常的重载解析,允许在每一层大括号进行转换)。 As such, (user-defined) conversions from the argument to the parameter type for each constructor of A are considered.因此,考虑A每个构造函数从参数到参数类型的(用户定义的)转换。

B

Current versions of GCC, ICC, and MSVC all reject this for ambiguity , since B can be converted to int or to A .当前版本的 GCC、ICC 和 MSVC 都因为歧义而拒绝这样做,因为B可以转换为intA (ICC helpfully points out that the move constructor is a better match than the copy constructor because of [over.ics.rank]/3.2.3, but there are still two choices.) It's hard to guess why Clang ignores the former possibility (what with no diagnostic output from it), but the other compilers appear to be correct : [dcl.init.list]/3.7 defers to normal overload resolution (except for preferring std::initializer_list constructors, which aren't relevant here), and there is no reason to prefer one constructor over the other (since a user-defined conversion sequence followed by an exact-match standard conversion sequence is involved in each case). (ICC 帮助指出,由于 [over.ics.rank]/3.2.3,移动构造函数比复制构造函数更匹配,但仍有两种选择。)很难猜测为什么 Clang 会忽略前一种可能性(什么没有诊断输出),但其他编译器似乎是正确的:[dcl.init.list]/3.7 遵循正常的重载解析(除了首选std::initializer_list构造函数,这在此处不相关),并且没有理由更喜欢一个构造函数而不是另一个(因为在每种情况下都涉及用户定义的转换序列,然后是精确匹配的标准转换序列)。

C<A>

Again, the deduction for the const A& or A&& constructors picks T = A (this time because of [temp.deduct.conv]'s simplifications rather than [over.match.copy]'s restrictions) and finds nothing.同样,对const A&A&&构造const A&的推导选择T = A (这次是因为 [temp.deduct.conv] 的简化而不是 [over.match.copy] 的限制)并且什么也没找到。 Therefore only the "conversion" C<A>intA works.因此只有“转换” C<A>intA有效。 All four compilers agree about this case, although MSVC erroneously issues a warning about an "illegal" double conversion.所有四个编译器都同意这种情况,尽管 MSVC 错误地发出了关于“非法”双重转换的警告。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM