在 clang 中显式指定的参数无效,但在 gcc 中成功编译 — 谁错了?

Invalid explicitly-specified argument in clang but successful compilation in gcc — who's wrong?

本文关键字:成功 gcc 编译 但在 错了 无效 clang 参数      更新时间:2023-10-16

以下代码在g++中编译没有问题:

#include <iostream>
#include <string>
#include <tuple>
template<typename T>
void test(const T& value)
{
    std::tuple<int, double> x;
    std::cout << std::get<value>(x);
}
int main() {
    test(std::integral_constant<std::size_t,1>());
}

我使用了这个命令:

g++ test.cpp -o test -std=c++14 -pedantic -Wall -Wextra

但当我将g++切换到clang++时(使用g++5.1.0和clang++3.6.0),我会得到以下错误:

test.cpp:9:18: error: no matching function for call to 'get'
    std::cout << std::get<value>(x);
                 ^~~~~~~~~~~~~~~
test.cpp:13:5: note: in instantiation of function template specialization 'test<std::integral_constant<unsigned long, 1> >' requested here
    test(std::integral_constant<std::size_t,1>());
         ^~~~~~~~~~~~~~~
<skipped>
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.1.0/../../../../include/c++/5.1.0/tuple:867:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '_Tp'
    get(tuple<_Types...>& __t) noexcept
    ^

以及用于std::get的其他过载的类似note:条目。

但我将std::integral_constant传递给test(),这是一个常量表达式,为什么它会是模板参数的"无效显式指定参数"?是叮当声还是我做错了什么?

我注意到,如果我将test()的参数从const T&更改为const T,则clang编译成功。我是否通过引用传递integral_constantconstexpr质量而丢失了它?

由于一周内没有答案,我将发布我的愿景。我远不是一个语言写作专家,实际上我认为自己是一个完全的新手,但仍然如此。以下是基于我对标准的阅读,以及我最近的问题。

因此,首先让我们以以下方式重写代码:

struct A {
    constexpr operator int() const { return 42; }
};
template <int>
void foo() {}
void test(const A& value) {
    foo<value>();
}
int main() {
    A a{};
    test(a);
}

它表现出相同的行为(使用gcc构建,使用clang失败并出现类似错误),但是:

  • test()没有模板类型推导,以确保问题与类型推导无关
  • 使用"mocks"而不是CCD_ 13成员来确保这不是他们实现的问题
  • 并且具有显式变量a,而不是临时变量,这将在后面解释

这里发生了什么?我将引用N4296。

我们有一个带有非类型参数的模板foo

[114.3.2(温度参数非类型)]/1:

非类型模板参数的模板参数应为类型的转换常量表达式(5.20)模板参数。

因此,模板参数,即value,应该是类型为int的转换常量表达式。

[5.20(expr.const)]/4:

T类型的转换常量表达式是一个隐式表达式转换为类型T,其中转换的表达式是常量表达式和隐式转换序列只包含

  • 用户定义的转换
  • 。。。(投下无关的子弹)

并且其中参考结合(如果有的话)直接结合。

我们的表达式(value)可以隐式转换为类型int,并且转换序列只包含用户定义的转换。因此,两个问题仍然存在:"转换后的表达式是否是常量表达式"answers"引用绑定(如果有)是否直接绑定"。

对于第一个问题,我认为短语"转换后的表达式"的意思是已经转换为int的表达式,这有点像static_cast<int>(value),而不是原始表达式(value)。为此,

[5.20(expr.const)]/2:

条件表达式e是核心常量表达式,除非CCD_ 24的评估遵循抽象机(1.9)的规则,将评估以下表达式之一:

  • 。。。(省略了一长串)

对我们的表达式static_cast<int>(value)的评估仅导致对A::operator int()的评估,即constexpr,因此是明确允许的。不评估A的任何成员(如果有的话),也不评估其他任何成员。

因此,static_cast<int>(value)是一个常数表达式。

关于第二个问题,关于引用绑定,我根本不清楚这指的是哪个过程。然而,无论如何,我们的代码中只有一个引用(const A& value),它直接绑定到main的变量a(这就是我引入a的原因)。

实际上,直接绑定是在[8.5.3(dcl.init.ref)]/5:末尾定义的

在除最后一种情况外的所有情况下(即,创建和初始化来自初始值设定项表达式的临时),则引用被称为直接绑定到初始值设定项表达式。

这个"最后一个"情况似乎指的是5.2,非直接绑定意味着从临时(如const int& i = 42;)初始化,而不是当我们有非临时a时的情况。

UPD:我问了一个单独的问题来检查我对上述标准的理解是否正确。


因此,底线是代码应该是有效的,而clang是错误的。我建议你向clang bug跟踪器提交一个bug,并参考这个问题。或者,无论出于何种原因,你都不会提交错误,请告诉我,我会提交的。

UPD:提交错误报告