无法在模板参数中进行函数调用
Unable to make function call in template argument
下面的程序可以正常编译。
#include <bitset>
#include <cmath>
int main()
{
const int r = std::sqrt(100);
std::bitset<r> n;
}
$ g++ -Wall -Wextra -pedantic -std=c++11 foo.cpp
$
但是下面的程序编译失败
#include <bitset>
#include <cmath>
int main()
{
std::bitset<std::sqrt(100)> n;
}
$ g++ -Wall -Wextra -pedantic -std=c++11 bar.cpp
bar.cpp: In function ‘int main()’:
bar.cpp:6:31: error: conversion from ‘__gnu_cxx::__enable_if<true, double>::__type {aka double}’ to ‘long unsigned int’ not considered for non-type template argument
std::bitset<std::sqrt(100)> n;
^
bar.cpp:6:31: error: could not convert template argument ‘std::sqrt<int>(100)’ to ‘long unsigned int’
bar.cpp:6:34: error: invalid type in declaration before ‘;’ token
std::bitset<std::sqrt(100)> n;
^
bar.cpp:6:33: warning: unused variable ‘n’ [-Wunused-variable]
std::bitset<std::sqrt(100)> n;
^
在我看来,这两个 C++ 程序是等价的。为什么第二个不能编译而第一个可以编译?
更新
一些答案说 std::sqrt()
通常不声明为 constexpr
但在 gcc 上通过声明 constexpr
扩展了它。但它仍然没有回答我的问题。
如果 std::sqrt()
没有声明为 constexpr
,那么两个程序都应该编译失败。
如果在 gcc 中将 std::sqrt()
声明为 constexpr
,那么两个程序都应该编译成功。
为什么只有第一个程序编译通过,而第二个程序编译失败?
第一个程序可能会为您编译,但它不可移植,因为 std::sqrt
函数未按标准指定为 constexpr
。看来 GCC 已经决定实现它 constexpr
:
template<typename _Tp>
inline _GLIBCXX_CONSTEXPR
typename __gnu_cxx::__enable_if<__is_integer<_Tp>::__value,
double>::__type
sqrt(_Tp __x)
{ return __builtin_sqrt(__x); }
但是另一个标准库实现可能没有 constexpr
std::sqrt
函数,因此您的第一个程序不会在那里编译。
让我们简化代码,并稍微修改一下,以便准确涉及相关概念:
constexpr std::size_t r = 10.0;
std::bitset<r> b1; // OK
std::bitset<10.0> b2; // ill-formed
看起来 b1
和 b2
的声明确实应该以相同的方式处理,但是模板参数的隐式转换规则比其他地方的隐式转换规则更严格.
根据标准,当模板参数的类型(此处为 double
)与传递给它的模板参数的类型(此处为 std::size_t
)不同时,仅允许一组有限的转换,即 "conversions permitted in a converted constant expression" ([temp.arg.nontype]/5)。根据[expr.const]/3,转换后的常量表达式只能涉及以下转换:
- 用户定义的转化
- 左值到右值的转换
- 积分促销
- 非收缩转换的整体转换
在此上下文中不允许浮点整数转换,即使在 r
的初始化中允许它也是如此。
下面的程序可以正常编译。
#include <bitset>
#include <cmath>
int main()
{
const int r = std::sqrt(100);
std::bitset<r> n;
}
$ g++ -Wall -Wextra -pedantic -std=c++11 foo.cpp
$
但是下面的程序编译失败
#include <bitset>
#include <cmath>
int main()
{
std::bitset<std::sqrt(100)> n;
}
$ g++ -Wall -Wextra -pedantic -std=c++11 bar.cpp
bar.cpp: In function ‘int main()’:
bar.cpp:6:31: error: conversion from ‘__gnu_cxx::__enable_if<true, double>::__type {aka double}’ to ‘long unsigned int’ not considered for non-type template argument
std::bitset<std::sqrt(100)> n;
^
bar.cpp:6:31: error: could not convert template argument ‘std::sqrt<int>(100)’ to ‘long unsigned int’
bar.cpp:6:34: error: invalid type in declaration before ‘;’ token
std::bitset<std::sqrt(100)> n;
^
bar.cpp:6:33: warning: unused variable ‘n’ [-Wunused-variable]
std::bitset<std::sqrt(100)> n;
^
在我看来,这两个 C++ 程序是等价的。为什么第二个不能编译而第一个可以编译?
更新
一些答案说 std::sqrt()
通常不声明为 constexpr
但在 gcc 上通过声明 constexpr
扩展了它。但它仍然没有回答我的问题。
如果 std::sqrt()
没有声明为 constexpr
,那么两个程序都应该编译失败。
如果在 gcc 中将 std::sqrt()
声明为 constexpr
,那么两个程序都应该编译成功。
为什么只有第一个程序编译通过,而第二个程序编译失败?
第一个程序可能会为您编译,但它不可移植,因为 std::sqrt
函数未按标准指定为 constexpr
。看来 GCC 已经决定实现它 constexpr
:
template<typename _Tp>
inline _GLIBCXX_CONSTEXPR
typename __gnu_cxx::__enable_if<__is_integer<_Tp>::__value,
double>::__type
sqrt(_Tp __x)
{ return __builtin_sqrt(__x); }
但是另一个标准库实现可能没有 constexpr
std::sqrt
函数,因此您的第一个程序不会在那里编译。
让我们简化代码,并稍微修改一下,以便准确涉及相关概念:
constexpr std::size_t r = 10.0;
std::bitset<r> b1; // OK
std::bitset<10.0> b2; // ill-formed
看起来 b1
和 b2
的声明确实应该以相同的方式处理,但是模板参数的隐式转换规则比其他地方的隐式转换规则更严格.
根据标准,当模板参数的类型(此处为 double
)与传递给它的模板参数的类型(此处为 std::size_t
)不同时,仅允许一组有限的转换,即 "conversions permitted in a converted constant expression" ([temp.arg.nontype]/5)。根据[expr.const]/3,转换后的常量表达式只能涉及以下转换:
- 用户定义的转化
- 左值到右值的转换
- 积分促销
- 非收缩转换的整体转换
在此上下文中不允许浮点整数转换,即使在 r
的初始化中允许它也是如此。