The following program compiles fine.
#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
$
But the following program fails to compile.
#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;
^
According to me, both C++ programs are equivalent. Why is it that then the second one does not compile whereas the first one does?
Some of the answers are saying that std::sqrt()
is generally not declared as constexpr
but on gcc has extended it by declaring it constexpr
. But it still does not answer my question.
If std::sqrt()
is not declared as constexpr
, then both programs should fail to compile.
If std::sqrt()
is declared as constexpr
in gcc, then both programs should compile successfully.
Why is it that only the first program compiles but the second one fails?
The first program might compile for you, but it is not portable because the std::sqrt
function is not specified by the standard to be constexpr
. It appears that GCC has decided to make it 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); }
But another standard library implementation might not have a constexpr
std::sqrt
function so your first program would not compile there.
Let us instead simplify the code, and modify it a bit so that the relevant concepts are involved exactly:
constexpr std::size_t r = 10.0;
std::bitset<r> b1; // OK
std::bitset<10.0> b2; // ill-formed
It really looks as though the declarations of b1
and b2
should be treated the same way, but the rules for implicit conversions of template arguments are more strict than the rules for implicit conversions elsewhere.
According to the standard, when the type of a template argument (here, double
) differs from the type of the template parameter it is being passed to (here, std::size_t
), only a restricted set of conversion is allowed, namely "conversions permitted in a converted constant expression" ([temp.arg.nontype]/5). According to [expr.const]/3, a converted constant expression can involve only the following conversions:
A floating-integral conversion is not allowed in this context, even though it is allowed in the initialization of r
.