clang 中明确指定的参数无效,但在 gcc 中编译成功——谁错了?
Invalid explicitly-specified argument in clang but successful compilation in gcc — who's wrong?
以下代码在 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()
,这是一个常量表达式,为什么模板参数会是 "invalid explicitly-specified argument"?是 clang bug 还是我做错了什么?
我注意到,如果我将 test()
的参数从 const T&
更改为 const T
,则 clang 会成功编译。通过引用传递,我是否以某种方式失去了 integral_constant
的 constexpr
质量?
由于一个星期都没有答案,我会post我的愿景。我远不是语言专家,实际上我会认为自己是一个完整的新手,但仍然如此。以下是根据我对标准的阅读,以及我最近的.
所以,首先让我们按照以下方式重写代码:
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' 而不是
std
成员来确保这不是他们实施的问题,
- 并且有一个显式变量
a
,不是临时变量,稍后解释。
这里发生了什么?我会引用 N4296.
我们有一个带有非类型参数的模板 foo
。
[14.3.2(temp.arg.nontype)]/1:
A template-argument for a non-type template-parameter shall be a
converted constant expression (5.20) of the type of the
template-parameter.
所以模板参数,即 value
,应该是类型 int
.
的转换常量表达式
[5.20(expr.const)]/4:
A converted constant expression of type T is an expression, implicitly
converted to type T, where the converted expression is a constant
expression and the implicit conversion sequence contains only
- user-defined conversions,
- ... (irrelevant bullets dropped)
and where the reference binding (if any) binds directly.
我们的表达式(value
)可以隐式转换为类型int
,并且转换序列只包含用户定义的转换。所以剩下两个问题:是否"the converted expression is a constant expression"和是否"the reference binding (if any) binds directly".
对于第一个问题,短语"the converted expression",我认为,表示已经转换为int
的表达式,类似于static_cast<int>(value)
,而不是原始表达式(value
).为此,
[5.20(expr.const)]/2:
A conditional-expression e
is a core constant expression unless the
evaluation of e
, following the rules of the abstract machine (1.9),
would evaluate one of the following expressions:
- ... (a long list omitted)
表达式 static_cast<int>(value)
的计算只会导致 A::operator int()
的计算,即 constexpr
,因此是明确允许的。没有 A
的成员(如果有的话)被评估,其他任何东西都没有被评估。
因此,static_cast<int>(value)
是常量表达式。
对于第二个问题,关于引用绑定,我根本不清楚这是指哪个过程。但是,无论如何我们的代码中只有一个引用(const A& value
),它直接绑定到main
的变量a
(这就是我引入[=13=的原因) ]).
确实直接绑定定义在[8.5.3(dcl.init.ref)]/5的最后:
In all cases except the last (i.e., creating and initializing a
temporary from the initializer expression), the reference is said to
bind directly to the initializer expression.
这种"last"情况似乎是指5.2,非直接绑定意味着从临时初始化(如const int& i = 42;
),而不是我们有非临时[=13]的情况=].
UPD:我问 检查我对上述标准的理解是否正确。
所以底线是代码应该是有效的,clang是错误的。我建议您将错误提交给 clang 错误跟踪器,并参考这个问题。或者,如果出于某种原因您不提交错误,请告诉我,我会提交。
UPD:归档a bug report
以下代码在 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()
,这是一个常量表达式,为什么模板参数会是 "invalid explicitly-specified argument"?是 clang bug 还是我做错了什么?
我注意到,如果我将 test()
的参数从 const T&
更改为 const T
,则 clang 会成功编译。通过引用传递,我是否以某种方式失去了 integral_constant
的 constexpr
质量?
由于一个星期都没有答案,我会post我的愿景。我远不是语言专家,实际上我会认为自己是一个完整的新手,但仍然如此。以下是根据我对标准的阅读,以及我最近的
所以,首先让我们按照以下方式重写代码:
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' 而不是
std
成员来确保这不是他们实施的问题, - 并且有一个显式变量
a
,不是临时变量,稍后解释。
这里发生了什么?我会引用 N4296.
我们有一个带有非类型参数的模板 foo
。
[14.3.2(temp.arg.nontype)]/1:
A template-argument for a non-type template-parameter shall be a converted constant expression (5.20) of the type of the template-parameter.
所以模板参数,即 value
,应该是类型 int
.
[5.20(expr.const)]/4:
A converted constant expression of type T is an expression, implicitly converted to type T, where the converted expression is a constant expression and the implicit conversion sequence contains only
- user-defined conversions,
- ... (irrelevant bullets dropped)
and where the reference binding (if any) binds directly.
我们的表达式(value
)可以隐式转换为类型int
,并且转换序列只包含用户定义的转换。所以剩下两个问题:是否"the converted expression is a constant expression"和是否"the reference binding (if any) binds directly".
对于第一个问题,短语"the converted expression",我认为,表示已经转换为int
的表达式,类似于static_cast<int>(value)
,而不是原始表达式(value
).为此,
[5.20(expr.const)]/2:
A conditional-expression
e
is a core constant expression unless the evaluation ofe
, following the rules of the abstract machine (1.9), would evaluate one of the following expressions:
- ... (a long list omitted)
表达式 static_cast<int>(value)
的计算只会导致 A::operator int()
的计算,即 constexpr
,因此是明确允许的。没有 A
的成员(如果有的话)被评估,其他任何东西都没有被评估。
因此,static_cast<int>(value)
是常量表达式。
对于第二个问题,关于引用绑定,我根本不清楚这是指哪个过程。但是,无论如何我们的代码中只有一个引用(const A& value
),它直接绑定到main
的变量a
(这就是我引入[=13=的原因) ]).
确实直接绑定定义在[8.5.3(dcl.init.ref)]/5的最后:
In all cases except the last (i.e., creating and initializing a temporary from the initializer expression), the reference is said to bind directly to the initializer expression.
这种"last"情况似乎是指5.2,非直接绑定意味着从临时初始化(如const int& i = 42;
),而不是我们有非临时[=13]的情况=].
UPD:我问
所以底线是代码应该是有效的,clang是错误的。我建议您将错误提交给 clang 错误跟踪器,并参考这个问题。或者,如果出于某种原因您不提交错误,请告诉我,我会提交。
UPD:归档a bug report