Consider the following example code:
#include <array>
struct MyClass
{
size_t value = 0;
constexpr static size_t size() noexcept
{
return 3;
}
};
template <size_t N>
void DoIt()
{
MyClass h;
std::array<int, h.size()> arr;
}
int main()
{
DoIt<1>();
}
When I try to compile this with GCC 7.3.0, I get an error about h not being usable in a non-constexpr context:
cexpr.cpp: In function ‘void DoIt()’:
cexpr.cpp:17:26: error: the value of ‘h’ is not usable in a constant expression
std::array<int, h.size()> arr;
^
cexpr.cpp:16:11: note: ‘h’ was not declared ‘constexpr’
MyClass h;
^
cexpr.cpp:17:27: error: the value of ‘h’ is not usable in a constant expression
std::array<int, h.size()> arr;
^
cexpr.cpp:16:11: note: ‘h’ was not declared ‘constexpr’
MyClass h;
^
However, when I try compiling the exact same code in Clang 6.0.0, it compiles without any errors. Additionally, when I modify the code to not be inside the templated DoIt() function, GCC compiles this just fine:
#include <array>
struct MyClass
{
size_t value = 0;
constexpr static size_t size() noexcept
{
return 3;
}
};
int main()
{
MyClass h;
// this compiles just fine in Clang and GCC
std::array<int, h.size()> arr;
}
I already know how to fix the first code so it compiles on GCC using decltype, but I'm curious to know why doesn't the first piece of code compile with GCC? Is this just a bug in GCC, or is there something I don't understand about using constexpr static member functions?
Looks like a bug to me.
The type and meaning of the expression h.size() is defined by [expr.ref] "Class member access":
[expr.post]/3
Abbreviating postfix-expression.id-expression as E1.E2, E1 is called the object expression. [...]
and
[expr.post]/6.3.1
If E2 is a (possibly overloaded) member function, function overload resolution is used to determine whether E1.E2 refers to a static or a non-static member function.
(6.3.1) If it refers to a static member function and the type of E2 is “function of parameter-type-list returning T”, then E1.E2 is an lvalue; the expression designates the static member function. The type of E1.E2 is the same type as that of E2, namely “function of parameter-type-list returning T”.
This means h.size has the same type as ::MyClass::size and is evaluated as such, regardless of the fact that h is constexpr or not.
h.size() is then a call to a constexpr function and is a core constant expression according to [expr.const]/4.
Related
Consider:
#include <variant>
struct A {
A() = default;
A(A&&) = delete;
};
struct B {
B() = delete;
B(A&&) {};
};
int main() {
std::variant<A, B> v{};
v = A{};
}
MSVC accepted it, while GCC and Clang rejected it with the same error message:
opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/12.0.0/../../../../include/c++/12.0.0/variant:1465:3: error: call to deleted member function 'operator='
operator=(variant(std::forward<_Tp>(__rhs)));
^~~~~~~~~
<source>:15:5: note: in instantiation of function template specialization 'std::variant<A, B>::operator=<A>' requested here
v = A{};
^
Which compiler should I trust?
EDIT
Initially, there was no language-lawyer tag, which was why I used cppreference for the analysis. However, looking into the latest draft (relevant section), I don't see anything that would make it invalid.
I believe MSVC is incorrect. According to the documentation:
Converting assignment.
Determines the alternative type T_j that would be selected by overload resolution for the expression F(std::forward<T>(t)) if there was an overload of imaginary function F(T_i) for every T_i from Types... in scope at the same time, except that:
An overload F(T_i) is only considered if the declaration T_i x[] = { std::forward<T>(t) }; is valid for some invented variable x;
With some imaginary function having forwarding reference parameter and calling it with A{} argument, A x[] = { std::forward<T>(t) }; is not valid while B x[] = { std::forward<T>(t) }; is. Consequently , T_j should be resolved as B. Live demo: https://godbolt.org/z/fM67e7oGj.
Then:
If *this already holds a T_j...
This does not apply, since v does not hold a B.
Next:
Otherwise, if std::is_nothrow_constructible_v<T_j, T> || !std::is_nothrow_move_constructible_v<T_j> is true...
This also does not apply, since this expression is false; live demo: https://godbolt.org/z/x674rnbcj (and, MSVC agrees on that: https://godbolt.org/z/5Techn8jG).
Finally:
Otherwise, equivalent to this->operator=(variant(std::forward<T>(t))).
However, this call does not compile with MSVC: https://godbolt.org/z/cWr4f6EhK.
In an attempt to call a function template which accepts a type and a parameter/argument of that type as the template parameters/arguments, compiler gives an error which is not produced with similar parameters/arguments. So I was wondering what is the correct parameters/arguments in case of calling the function templates for the member function "operator[]const" of a vector class!
Consider this piece of code:
class test_class{
public:
int member;
int& operator[](size_t) {return member;}
const int& operator[](size_t) const{return member;}
};
typedef std::vector<int> vector_type;
typedef const int&(test_class::* OK_type)(size_t)const;
typedef const int&(vector_type::* not_OK_type)(size_t)const;
static constexpr OK_type OK_pointer = &test_class::operator[];
static constexpr not_OK_type not_OK_pointer = &vector_type::operator[];
template<typename t__, t__>
void function(){}
The above code is alright now consider the main function:
int main() {
function<OK_type, OK_pointer>();
function<not_OK_type, not_OK_pointer>();
return 0;
}
The first call of the function template is OK but not the second one.
The error which compiler produce is:
error: no matching function for call to ‘function<not_OK_type, not_OK_pointer>()’
note: candidate: ‘template<class t__, t__ <anonymous> > void function()’
note: template argument deduction/substitution failed:
error: ‘const int& (std::vector<int>::*)(size_t) const{((const int& (std::vector<int>::*)(size_t) const)std::vector<int>::operator[]), 0}’ is not a valid template argument for type ‘const int& (std::vector<int>::*)(long unsigned int) const’
function<not_OK_type, not_OK_pointer>();
note: it must be a pointer-to-member of the form ‘&X::Y’
Interestingly even if the function template was formed as:
template<auto>
void function(){}
it would cause the same result.
I must add that in the case of non const version, error is the same (for std::vector).
So I am wondering
A: what is wrong?
B: Considering that if there was a mismatch between the not_OK_type and &vector_type::operator[], then compiler would also give an error in case of:
static constexpr not_OK_type not_OK_pointer = &vector_type::operator[];
Is there a difference between types that can be used as constexpr and the type that can be used as a template parameter/argument?
The issue is typedef const int&(vector_type::* not_OK_type)(size_t)const;.
If you see stl_vector.h (here), the operator[] is declared as noexcept at line 1040.
But in the declaration of not_OK_type variable, noexcept is not present. That's why the compiler complains.
For getting rid of compilation error, add noexcept to not_OK_type variable. Like this:
typedef const int&(vector_type::* not_OK_type)(size_t)const noexcept;
Working code:
#include <vector>
class test_class{
public:
int member;
int& operator[](size_t) {return member;}
const int& operator[](size_t) const{return member;}
};
typedef std::vector<int> vector_type;
typedef const int&(test_class::* OK_type)(size_t)const;
typedef const int&(vector_type::* not_OK_type)(size_t)const noexcept;
static constexpr OK_type OK_pointer = &test_class::operator[];
static constexpr not_OK_type not_OK_pointer = &vector_type::operator[];
template<typename t__, t__>
void function(){}
int main() {
function<OK_type, OK_pointer>();
function<not_OK_type, not_OK_pointer>();
return 0;
}
#Kunal Puri, provided a correct answer for the the section A of the question.
As for the section B of the question, I guess the clue can be found in one exception that applies when converted constant expression used as template argument for a non-type template parameter.
According to (https://en.cppreference.com/w/cpp/language/constant_expression), a converted constant expression under certain condition and specifically in case of conversion of pointer to noexcept function to pointer to function is a constant expression. Which explains why compiler did not produce an error in case of:
static constexpr not_OK_type not_OK_pointer = &vector_type::operator[];
However according to (https://en.cppreference.com/w/cpp/language/template_parameters), this type of converted constant expression can not be used as a pointer to non-static data members(and maybe also a pointer to non-static member functions) for a non-type template parameter.
This exception might be the source of conflict between types that can be used as constexpr and as template argument, although statements in the later source are vague and not directly linked to the case.
This very simple code code gives an error in GCC 6.0:
template<class T>
struct S {
// error: cannot convert 'T' to 'const int' in initialization
static const int b = T{};
};
int main() {
}
Strangely, if I use regular braces instead (T()) then the code compiles. Is this a bug? The code compiles fine in clang.
The reason that T() works is because the compiler interprets it as a function declaration which takes no argument. The compiling would be done with just an explicit casting:
static const int b = (const int) T{};
I have the following piece of code which represents an actual bigger piece of code:
#include <iostream>
using namespace std;
template<size_t N> class A {
public:
static constexpr size_t getN() {return N;}
};
template<size_t N> class B {
public:
void print() { cout << "B created: " << N << '\n';}
};
template <class T> class C {
public:
void set(T* a) {
t_ptr = a;
}
void create() {
constexpr int m = t_ptr->getN();
B<m> b;
b.print();
}
private:
T* t_ptr;
};
int main() {
constexpr int n = 2;
A<n> a;
C<A<n> > c;
c.set(&a);
c.create();
}
Compiling with g++ -o main main.cpp -std=c++11 and GCC/G++ 4.8.3 the expected output is received:
B created: 2
However, with GCC/G++ 4.9.1 the code does not compile, output:
main.cpp: In member function ‘void C<T>::create()’:
main.cpp:27:15: error: the value of ‘m’ is not usable in a constant expression
B<m> b;
^
main.cpp:26:27: note: ‘m’ used in its own initializer
constexpr int m = t_ptr->getN();
^
main.cpp:27:16: error: the value of ‘m’ is not usable in a constant expression
B<m> b;
^
main.cpp:26:27: note: ‘m’ used in its own initializer
constexpr int m = t_ptr->getN();
^
main.cpp:27:19: error: invalid type in declaration before ‘;’ token
B<m> b;
^
main.cpp:28:15: error: request for member ‘print’ in ‘b’, which is of non-class type ‘int’
b.print();
^
This is caused by a known bug in GCC 4.9: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59937 and in this older thread https://gcc.gnu.org/ml/gcc-bugs/2013-11/msg00067.html the usage of extern is proposed as a workaround. However, I am not able to get this workaround working.
Could you guys help me to make this code compile in GCC 4.9? Thank you!
Since this is not constexpr the access to this->t_ptr is not either.
clang's error is a bit more helpful
implicit use of 'this' pointer is only allowed within the
evaluation of a call to a 'constexpr' member function
Referring to:
N3690 5.19/2 (emphasis added)
A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:
— this, except in a constexpr function or a constexpr constructor that is being evaluated as
part of e;
Calling the static member function via the typename works
constexpr int m = T::getN();
This question already has answers here:
Is there any difference between "T" and "const T" in template parameter?
(3 answers)
Closed 9 years ago.
What is the effect of the const keyword in this template?
template <class T, int const ROWNUM, int const COLNUM>
class Matrix
Does it mean that this template only accept a const as parameter? If so, is there a way to pass a variable as the COLNUM and ROWNUM?
(when I try to pass a variable as the COLNUM for the template, it gives an error: "IntelliSense: expression must have a constant value")
It's ignored:
[C++11: 14.1/4]: A non-type template-parameter shall have one of the following (optionally cv-qualified) types:
integral or enumeration type,
pointer to object or pointer to function,
lvalue reference to object or lvalue reference to function,
pointer to member,
std::nullptr_t.
[C++11: 14.1/5]: [ Note: Other types are disallowed either explicitly below or implicitly by the rules governing the form of template-arguments (14.3). —end note ] The top-level cv-qualifiers on the template-parameter are ignored when determining its type.
The same wording is present at the same location in C++03.
This is partially because template arguments must be known at compile-time anyway. So, whether you have the const there or not, you may not pass some variable value:
template <int N>
void f()
{
N = 42;
}
template <int const N>
void g()
{
N = 42;
}
int main()
{
f<0>();
g<0>();
static const int h = 1;
f<h>();
g<h>();
}
prog.cpp: In function ‘void f() [with int N = 0]’:
prog.cpp:15: instantiated from here
prog.cpp:4: error: lvalue required as left operand of assignment
prog.cpp: In function ‘void g() [with int N = 0]’:
prog.cpp:16: instantiated from here
prog.cpp:10: error: lvalue required as left operand of assignment
prog.cpp: In function ‘void f() [with int N = 1]’:
prog.cpp:19: instantiated from here
prog.cpp:4: error: lvalue required as left operand of assignment
prog.cpp: In function ‘void g() [with int N = 1]’:
prog.cpp:20: instantiated from here
prog.cpp:10: error: lvalue required as left operand of assignment
const is not required in your case
for instance, both classes Matrix_A and Matrix_B below are the same for the compiler point of view. const here is just to enforce the fact that ROWNUM and COLNUM are constant for humans point of view, but not required.
template <class T, int const ROWNUM, int const COLNUM>
class Matrix_A
{
};
template <class T, int ROWNUM, int COLNUM>
class Matrix_B
{
};
Moreover following class Matrix_C also specify similar constant variables ROWNUM and COLNUM in another way:
template <class T>
class Matrix_C
{
static int const ROWNUM = 5;
static int const COLNUM = 20;
};
// the following three objects use constant variables ROWNUM and COLNUM
Matrix_A<bool,5,20> a;
Matrix_B<bool,5,20> b;
Matrix_C<bool> c;