I am trying to implement in a C++ class a constexpr member function which returns a template parameter. The code is supposed to be c++11 compatible. However I encounter compilation issues when the templated class also contains STL containers as data members such as std::vector (which are untouched by the constexpr member function).
A minimal example is given by the following code:
#include <vector>
#include <iostream>
#include <array>
template<size_t n>
struct A
{
constexpr size_t dimensions() const
{
return n;
}
private:
std::vector<double> a;
};
int main(int argc,char ** argv)
{
auto a=A<3>();
std::array<double,a.dimensions()> arr;
}
The code compiles correctly with the commands
g++ -std=c++14 -O3 quickTest.cpp -o test -Wall
clang++ -std=c++11 -O3 quickTest.cpp -o test -Wall
but fails when I use
g++ -std=c++11 -O3 quickTest.cpp -o test -Wall
with the error
quickTest.cpp:22:33: error: call to non-‘constexpr’ function ‘size_t A<n>::dimensions() const [with long unsigned int n = 3; size_t = long unsigned int]’
std::array<double,a.dimensions()> arr;
~~~~~~~~~~~~^~
quickTest.cpp:10:20: note: ‘size_t A<n>::dimensions() const [with long unsigned int n = 3; size_t = long unsigned int]’ is not usable as a ‘constexpr’ function because:
constexpr size_t dimensions() const
^~~~~~~~~~
quickTest.cpp:22:33: error: call to non-‘constexpr’ function ‘size_t A<n>::dimensions() const [with long unsigned int n = 3; size_t = long unsigned int]’
std::array<double,a.dimensions()> arr;
~~~~~~~~~~~~^~
quickTest.cpp:22:33: note: in template argument for type ‘long unsigned int’
Why does the code not compile with gcc -std=c++11 but does compile with clang++ -std=c++11 ?
How could one make this code snippet work with older versions of gcc which many not support c++14/17, but only c++11 ?
I am using gcc 8.1.1 and clang 6.0.1
C++11 had a rule [dcl.constexpr]/8:
... The class of which that function is a member shall be a literal type ([basic.types]).
struct A is not a literal type because of the vector, hence its non-static member functions cannot be constexpr.
So GCC is right to reject the code in C++11 mode.
C++14 removed that restriction.
The solution for C++11 is to declare dimensions() static:
static constexpr size_t dimensions()
{
return n;
}
Live demo
Related
I'm working on a custom wrapper for std::vector
#include <vector>
template<typename Key, typename Value>
struct keyed_vector {
typedef Value value_type;
typedef Key size_type;
std::vector<Value> values;
keyed_vector( std::initializer_list<Value> init ) : values(init) {}
size_type size() const { return static_cast<size_type>(values.size()); }
};
I wanted to make sure that the compiler error when trying to use the return
value of size() as a size_t was sane, so I wrote a quick test:
#include <cassert>
int main() {
struct my_size_type {
size_t t;
explicit my_size_type(size_t t) : t(t) {}
};
keyed_vector<my_size_type, int> vec{};
{
size_t actual_size {vec.size().t}; // this should compile cleanly
assert(actual_size == 0u);
}
{
size_t actual_size {vec.size()}; // this should be a compiler error
assert(actual_size == 0u);
}
}
And while the compiler did fail, the error message I got was different than I expected:
$ g++ --stdlib=libc++ -std=c++1y -Wall -Wextra -Werror keyed_vector_example.cpp
keyed_vector_example.cpp:28:10: error: no viable conversion from 'void' to 'size_t' (aka 'unsigned long')
size_t actual_size {vec.size()}; // this should be a compiler error
^ ~~~~~~~~~~~~
1 error generated.
I would have expected "error: no viable conversion from 'my_size_type' to 'size_t' (aka 'unsigned long')".
Why is the compiler (Apple LLVM version 8.1.0 (clang-802.0.42)) reporting the
return type of vec.size() as void here?
Ah, I think I found the reason in the comments to another question. It's because I'm using an initializer list.
If I switch the code to:
size_t actual_size = vec.size(); // this should be a compiler error
Then I get the much clearer error:
$ g++ --stdlib=libc++ -std=c++1y -Wall -Wextra -Werror keyed_vector_example.cpp
keyed_vector.cpp:28:10: error: no viable conversion from 'size_type' (aka 'my_size_type') to 'size_t' (aka 'unsigned long')
size_t actual_size = vec.size(); // this should be a compiler error
^ ~~~~~~~~~~
1 error generated.
It looks like other compilers give a better error message for braced initializer lists, so if this really bothers me I should file an issue with clang.
The code below compiles correctly on clang 3.8.1-1 on ArchLinux.
Is this clang bug?
gcc issue correct warning/error on this.
template <class T>
struct BugReproducer{
using size_type = typename T::size_type;
int bug1(size_type count);
int bug2(size_type count) const;
static int bug3(size_type count);
};
template <class T>
int BugReproducer<T>::bug1(size_type const count){
// this is a bug. must be not allowed
count = 5;
// return is to use the result...
return count;
}
template <class T>
int BugReproducer<T>::bug2(size_type const count) const{
// same for const method
count = 5;
return count;
}
template <class T>
int BugReproducer<T>::bug3(size_type const count){
// same for static method
count = 5;
return count;
}
struct DummyVector{
using size_type = int;
};
int main(){
using BugRepr = BugReproducer<DummyVector>;
BugRepr reproducer;
auto a = reproducer.bug1(1);
auto b = reproducer.bug2(1);
auto c = BugRepr::bug3(1);
// return is to use the result...
return a + b + c;
}
Here how I compile:
[nmmm#zenbook HM3]$ clang x.cc -std=c++11 -lstdc++ -Wall -Wpedantic -Wconversion
clang and c++14 - same result.
[nmmm#zenbook HM3]$ clang x.cc -std=c++14 -lstdc++ -Wall -Wpedantic -Wconversion
Here is gcc output:
[nmmm#zenbook HM3]$ gcc x.cc -std=c++11 -lstdc++ -Wall -Wpedantic -Wconversion
x.cc: In instantiation of ‘int BugReproducer<T>::bug1(BugReproducer<T>::size_type) [with T = DummyVector; BugReproducer<T>::size_type = int]’:
x.cc:46:28: required from here
x.cc:13:8: error: assignment of read-only parameter ‘count’
count = 5;
~~~~~~^~~
x.cc: In instantiation of ‘int BugReproducer<T>::bug2(BugReproducer<T>::size_type) const [with T = DummyVector; BugReproducer<T>::size_type = int]’:
x.cc:47:28: required from here
x.cc:22:8: error: assignment of read-only parameter ‘count’
count = 5;
~~~~~~^~~
x.cc: In instantiation of ‘static int BugReproducer<T>::bug3(BugReproducer<T>::size_type) [with T = DummyVector; BugReproducer<T>::size_type = int]’:
x.cc:48:20: required from here
x.cc:29:8: error: assignment of read-only parameter ‘count’
count = 5;
~~~~~~^~~
Yes, this is a bug in clang; filed at https://llvm.org/bugs/show_bug.cgi?id=30365.
The nature of the bug is that in a class template member function definition appearing outside ([class.mfct]/1) the class template, with the type of a parameter dependent on the class template parameters, clang uses the parameter type of the declaration rather than the parameter type of the definition where they differ in topmost cv-qualification. Simplified example:
template<class T> struct A { void f(typename T::U); };
template<class T> void A<T>::f(typename T::U const i) { i = 1; }
struct X { using U = int; };
int main() { A<X>{}.f(0); }
Per [dcl.fct]/5 the type of i within the definition of A<X>::f is int const (Use of 'const' for function parameters):
5 - [...] After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter
type are deleted when forming the function type. [...]
[ Note: This transformation does not affect the types of the parameters. [...] — end note ]
Trying to compile the following code in GCC 4.8:
#include <iostream>
#include <string>
template<class T>
class Bar {
public:
std::string test = "test";
};
int main() {
Bar<int> bar;
return 0;
}
Using g++ --std=c++11 -o main main.cpp gives the following error:
main.cpp: In constructor ‘constexpr Bar<int>::Bar()’:
main.cpp:5:7: error: conversion from ‘const char [5]’ to non-scalar type ‘std::string {aka std::basic_string<char>}’ requested
class Bar {
^
main.cpp: In function ‘int main()’:
main.cpp:11:14: note: synthesized method ‘constexpr Bar<int>::Bar()’ first required here
Bar<int> bar;
^
However, it compiles correctly in the following cases:
Using GCC 5.2
Replacing std::string with int (and using an integer value on the right side)
Not using a template class
Is it a bug in GCC 4.8? I can't find a way to install GCC 5.2 on my server (I need to compile it for CentOS 6.8) so I believe I'm stuck here.
I encountered a problem when using constexpr functions together with lambdas.
The following code is a minimal version which reproduces the error:
#include <iostream>
constexpr unsigned bar(unsigned q) {
return q;
}
template<unsigned N>
unsigned foo() {
return N;
}
template<typename F>
void print(F f) {
std::cout << f() << std::endl;
}
template<unsigned Q>
int stuff() {
constexpr unsigned n = bar(Q);
print([]() { return foo<n>(); });
}
int main() {
stuff<13>();
}
When compiling with gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) there are the following compiler errors:
constexpr_template.cpp: In lambda function:
constexpr_template.cpp:24:9: instantiated from ‘stuff() [with unsigned int Q = 13u]::<lambda()>’
constexpr_template.cpp:24:2: instantiated from ‘int stuff() [with unsigned int Q = 13u]’
constexpr_template.cpp:29:12: instantiated from here
constexpr_template.cpp:24:32: error: no matching function for call to ‘foo()’
constexpr_template.cpp:24:32: note: candidate is:
constexpr_template.cpp:9:10: note: template<unsigned int N> unsigned int foo()
Now the strange part is, if constexpr unsigned n = bar(Q); is changed into constexpr unsigned n = Q; it works.
What is also working is print([]() { return foo<bar(Q)>(); });...
Is this a bug in GCC or what am I doing wrong?
Gcc 4.6 was the first version to support constexpr, and it is not unusual for minor bugs to be present upon release of such features. You can verify from this Live Example on Coliru that gcc 4.8.1 and Clang 3.4 SVN correctly parse your code. You probably should upgrade your compiler accordingly.
I have the following template function:
template <std::size_t first, std::size_t last, typename T>
bool in_range(T& in)
{
for(auto i = in.begin(); i!=in.end(); ++i)
if(*i<first || *i>last)
return false;
return true;
}
but when I try to use it as such:
std::vector<int> test;
test.push_back(1);
test.push_back(5);
test.push_back(6);
std::cout<<in_range<4,7>(test);
I get this weird error:
main.cpp: In instantiation of 'bool in_range(T&) [with long long unsigned int first = 4ull; long long unsigned int last = 7ull; T = std::vector<int>]':
main.cpp:31:34: required from here
What am I doing wrong?
EDIT: full build log: http://pastebin.com/Cwemq2Hk
If I build that with C++11 support enabled, then it compiles. Here is a demonstation.
Before C++11, auto had a different meaning, and so auto i = ... was invalid - it declared a variable with no type.
I guess you're using GCC; depending on the version, you'll need to specify either -std=c++0x or -std=c++11 as a command-line option.