Search code examples
c++constantsbitset

Unable to make function call in template argument


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?


Solution

  • 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.