0

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?

Update

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?

Lone Learner
  • 18,088
  • 20
  • 102
  • 200
  • [`std::sqrt`](http://en.cppreference.com/w/cpp/numeric/math/sqrt) is not declared as [`constexpr`](http://en.cppreference.com/w/cpp/language/constexpr) – W.F. Mar 29 '17 at 07:21
  • @W.F. If that were the reason, then the first program should also not compile. – Brian Bi Mar 29 '17 at 07:21
  • see e.g. clang does **not** [compile](https://wandbox.org/permlink/3ve7eWoh71dNH0ev) the code – W.F. Mar 29 '17 at 07:24
  • Most likely it is a gcc extension (or bug?)... – W.F. Mar 29 '17 at 07:24
  • [Related](http://stackoverflow.com/questions/8622256/in-c11-is-sqrt-defined-as-constexpr)... if you decide to write your own constexpr sqrt – W.F. Mar 29 '17 at 07:37

1 Answers1

3

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:

  • user-defined conversions
  • lvalue-to-rvalue conversions
  • integral promotions
  • integral conversions other than narrowing conversions

A floating-integral conversion is not allowed in this context, even though it is allowed in the initialization of r.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • Thanks the detailed answer. The problem I face in my code is that I don't know that `r = 10` in advance. In reality, in my case, `r = std::sqrt(another_var)`. Is there a way to still simplify my code? – Lone Learner Mar 29 '17 at 07:46
  • @LoneLearner If you are fine with your code only compiling on gcc then do `constexpr int r = __builtin_sqrt(another_var);` and then use `r` as a template argument. – Brian Bi Mar 29 '17 at 08:02
  • if gcc has defined `std::sqrt()` to be `constexpr`, then both the first and second code examples should compile? Why is it that only the first one compiles and the second one does not? – Lone Learner Mar 30 '17 at 01:59
  • @LoneLearner I explained already. The second example does not compile because the floating-integral conversion is "inside" the binding of the argument to the template parameter. If it is "outside", then it's fine. – Brian Bi Mar 30 '17 at 02:03
  • Thanks! Indeed if I change the second example to use `int` instead of `double`, the second example compiles too. For example, this code compiles: `std::bitset(std::sqrt(100))> n;` – Lone Learner Mar 30 '17 at 02:09