为什么模板函数参数不适用隐式转换?

6

我遇到了一个与模板相关的问题,我已经缩小到以下示例(C++17):

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

void conversionToConstRefWorks (const item<const int> &) { }

template <typename T> 
void butNotWhenTemplated (const item<const T> &) { }

int main () {

  item<int> i;
  item<const int> ci;

  // these all compile fine:
  conversionToConstRefWorks(ci);
  conversionToConstRefWorks(i);
  butNotWhenTemplated(ci);

  // but this one fails:
  butNotWhenTemplated(i); 

}

在这个例子中:
- `item` 有一个隐式转换运算符到 `item`,并且 - 在 `conversionToConstRefWorks()` 中转换似乎起作用,但是 - 在 `butNotWhenTemplated()` 中转换被忽略了,在那里一个 `item` 可以很好地传递,但是传递一个 `item` 会导致编译错误。
这个例子的编译失败(GCC 9.3)是:
g++ --std=c++17 -W -Wall -pedantic -Wno-unused-variable    const_interop.cpp   -o const_interop
const_interop.cpp: In function ‘int main()’:
const_interop.cpp:54:24: error: no matching function for call to ‘butNotWhenTemplated(item<int>&)’
   54 |   butNotWhenTemplated(i);
      |                        ^
const_interop.cpp:40:6: note: candidate: ‘template<class T> void butNotWhenTemplated(const item<const T>&)’
   40 | void butNotWhenTemplated (const item<const T> &) {
      |      ^~~~~~~~~~~~~~~~~~~
const_interop.cpp:40:6: note:   template argument deduction/substitution failed:
const_interop.cpp:54:24: note:   types ‘const T’ and ‘int’ have incompatible cv-qualifiers
   54 |   butNotWhenTemplated(i);
      |                        ^

根本错误似乎是:

类型“const T”和“int”的cv限定符不兼容

我从字面上理解了这句话,但我不明白为什么会发生这种情况。我的期望是,在调用butNotWhenTemplated(i)时,item<int> :: operator item<const int> () const转换运算符将被应用,就像在调用conversionToConstRefWorks(i)时一样,并且T将选择int
我的主要问题是:为什么这不编译? 我的另一个问题是:出于本帖子范围之外的原因,butNotWhenTemplated必须是一个模板,并且必须对所有item参数指定<const T>,而我不能在调用它时显式地指定模板参数。有没有办法在这些约束条件下使其工作? 点此在ideone上查看(GCC 8.3)。

但是以标准的名义,为什么 这个 能够工作呢?(我使用 T* 代替了 Item<T> - ph3rin
@RinKaenbyou 除非我读错/误解了,我认为这是因为在那里,int *int const * const & 转换不是在模板参数内完成的(与 Item<T>Item<const T> 不同 -- 你的更类似于 Item<T>const Item<T>)。例如,Tconst T 是仅在 cv 限定符上有所不同的相同类型,而 Item<T>Item<const T> 是完全不同的类型,因为模板参数不同,然后一些推导规则让它可以。也许吧。另一方面,我不知道我在说什么。 - Jason C
2个回答

9
item<int> i;
template <typename T> void butNotWhenTemplated (const item<const T> &) { }
butNotWhenTemplated(i); 

根据模板参数替换规则,无法找到item<const T>item<int>匹配的T。在任何转换(内置或用户定义)考虑之前,这将以硬错误的方式失败。

类型推导不考虑隐式转换(除了上面列出的类型调整):这是后续重载决议的工作。然而,如果对参与模板参数推导的所有参数都成功进行了推导,并且所有未推导的模板参数都已明确指定或默认值,则剩余的函数参数将与相应的函数参数进行比较。


嗯...那真糟糕。很遗憾,在推导发生之前没有办法告诉编译器哪些转换是可能的。 :( - Jason C
1
@JasonC 你总是可以采用一些技巧:http://coliru.stacked-crooked.com/a/b34585169034b311 - YSC
我不讨厌那个想法。我很快就会试试它。 - Jason C

4

尝试使用这个重载:

template <typename T>
void butNotWhenTemplated(const item<const T>&) { }

template <typename T>
void butNotWhenTemplated(const item<T>& x) {
    butNotWhenTemplated<const T>(x);
}

补充:

你正在尝试将引用传递给const,但是即使在非模板情况下,隐式转换也会创建对象的副本。你可能需要重新考虑你的设计。


我的真实代码有多个参数,这些参数的const属性可能会变化,但必须始终具有相同的基本类型;我能够使用https://ideone.com/8A1KC9类似于您的想法。 - Jason C
1
这在使用Visual C ++时对我没有递归。AFAIK重载解析应该优先考虑const修饰的版本。也许你的代码中有些不同? - Gyross
我认为我在我的代码中确实做了一些不同的事情;因为我刚刚再次尝试了你的代码,并且它运行良好。现在我正在努力想起我所做的使递归发生的事情,这真让我烦恼。 - Jason C
https://ideone.com/YbaKSz确实会卡住,这与我在第一条评论中提供的链接相同,只是函数名称相同。我真的不知道为什么会这样。我的头很疼,但无论如何,我喜欢这种方法。 - Jason C
1
test(i, c, c)和test(c, c, c)都可以工作,所以我认为编译器认为您只想指定第一个类型,用这种方式做是可行的:https://ideone.com/Yg1YC8 - Gyross
我喜欢它。我最终会掌握这个的。谢谢。 - Jason C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接