Class vs. function template specialization [duplicate] - c++

This question already has answers here:
C++ template specialization for pointer?
(3 answers)
Closed 2 years ago.
I only recently learned about partial template specialization in C++ from here, and it perfectly solved a problem where I needed a template class to behave differently for pointers and non-pointers. A simplified example is:
// main.cpp
template <typename T>
class C
{
public:
C( const T& t ) : t_( t ) {}
private:
T t_;
};
template <typename T>
class C<T*>
{
public:
C( const T& t ) : t_( new T(t) ) {}
~C() { delete t_; }
private:
T* t_;
};
int main( int argc, char* argv[] )
{
C<int> c1(4);
C<int*> c2(2);
return 0;
}
I wanted to try extend this idea to functions, but ran into compiler errors I didn't understand:
// main.cpp
#include <set>
template <typename T>
std::set<T> makeSet( size_t off )
{
std::set<T> s;
s.insert( T() + T(off) );
return s;
}
template <typename T>
std::set<T*> makeSet<T*>( size_t off )
{
std::set<T*> s;
T* t = new T( T() + T(off) );
s.insert( t );
return s;
}
int main( int argc, char* argv[] )
{
std::set<int> s1 = makeSet<int>(4);
std::set<int*> s2 = makeSet<int*>(2);
delete *(s2.begin());
return 0;
}
.
$ g++ --version && g++ -g ./main.cpp
g++ (GCC) 9.2.1 20190827 (Red Hat 9.2.1-1)
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
./main.cpp:13:38: error: non-class, non-variable partial specialization ‘makeSet<T*>’ is not allowed
13 | std::set<T*> makeSet<T*>( size_t off )
| ^
./main.cpp: In function ‘int main(int, char**)’:
./main.cpp:23:37: error: call of overloaded ‘makeSet<int>(int)’ is ambiguous
23 | std::set<int> s1 = makeSet<int>(4);
| ^
./main.cpp:5:13: note: candidate: ‘std::set<T> makeSet(size_t) [with T = int; size_t = long unsigned int]’
5 | std::set<T> makeSet( size_t off )
| ^~~~~~~
./main.cpp:13:14: note: candidate: ‘std::set<T*> makeSet(size_t) [with T = int; size_t = long unsigned int]’
13 | std::set<T*> makeSet<T*>( size_t off )
| ^~~~~~~~~~~
./main.cpp:24:38: error: call of overloaded ‘makeSet<int*>(int)’ is ambiguous
24 | std::set<int*> s2 = makeSet<int*>(2);
| ^
./main.cpp:5:13: note: candidate: ‘std::set<T> makeSet(size_t) [with T = int*; size_t = long unsigned int]’
5 | std::set<T> makeSet( size_t off )
| ^~~~~~~
./main.cpp:13:14: note: candidate: ‘std::set<T*> makeSet(size_t) [with T = int*; size_t = long unsigned int]’
13 | std::set<T*> makeSet<T*>( size_t off )
| ^~~~~~~~~~~
I was a little skeptical about this because it looks like an attempt at overloading a function with a different return-type but with the same arguments - I know this is otherwise illegal, but I thought that because these were template functions, specifying the template type when invoking the functions (as in the above main()) would allow disambiguation.
But I'm not sure that's what the compiler is complaining about on line 13: what does the non-class, non-variable partial specialization ‘makeSet<T*>’ is not allowed error mean?
Is what I'm trying to do even possible in C++? I.e. can a template function be made so that it behaves differently for pointers and non-pointer template types and return a STL container of the specified type? (Would the answer be different between C++ 98, 03, 11, and 14?)

what does the non-class, non-variable partial specialization ‘makeSet<T*>’ is not allowed error mean?
Let's take a look at some documentation from cppreference.com regarding partial template specialization:
Allows customizing class [and variable (since C++14)] templates for a given category of template arguments.
Partial specializations are allowed for class and variable templates. The compiler has told you that your template is for neither a class for a variable. Therefore, partial specialization is not allowed.
A less confusing wording for the compiler's message would be: partial specialization of function templates is not allowed.
As for what can be done, it is possible to declare a class template with a suitable operator(). In some cases this is about as good as using a function template.

You can try something like this (C++17):
template <typename T>
std::set<T> makeSet( size_t off )
{
std::set<T> s;
if constexpr(std::is_pointer_v<T>)
{
using U = std::remove_reference_t<decltype(*std::declval<T>())>;
s.insert(new U( U() + U(off) ));
}
else
{
s.insert( T() + T(off) );
}
return s;
}

Related

C++: Why can't I invoke a template member function of a template parameter type? [duplicate]

This question already has answers here:
Where and why do I have to put the "template" and "typename" keywords?
(8 answers)
Closed 6 months ago.
I am writing a template function where one of the template parameters is a type with a member function that is itself a template function. When I invoke the template member function and explicitly specify the template parameters, it appears that the code does not compile. This is illustrated in the following minimal example:
This version will compile and run just fine:
#include <iostream>
struct ar_t
{
int data[2];
ar_t(void) {data[0] = 10; data[1] = 17;}
template <const std::size_t idx> int get(void) const {return data[idx];}
};
template <const std::size_t val> struct idx_t {};
template <const std::size_t val> int idx_ar1(const ar_t& ar, const idx_t<val>& idx)
{
return ar.get<val>();
}
int main(int argc, char** argv)
{
ar_t x;
const std::size_t index = 1;
idx_t<index> i;
idx_ar1(x,i);
return 0;
}
whereas this version will not:
#include <iostream>
struct ar_t
{
int data[2];
ar_t(void) {data[0] = 10; data[1] = 17;}
template <const std::size_t idx> int get(void) const {return data[idx];}
};
template <const std::size_t val> struct idx_t {};
template <typename arr_type, const std::size_t val> int idx_ar1(const arr_type& ar, const idx_t<val>& idx)
{
return ar.get<val>();
}
int main(int argc, char** argv)
{
ar_t x;
const std::size_t index = 1;
idx_t<index> i;
idx_ar1(x,i);
return 0;
}
Note the difference in the template parameters for idx_ar1. The error message I get with g++ 11.1 and -std=c++20 is:
main.cc: In function ‘int idx_ar1(const arr_type&, const idx_t<val>&)’:
main.cc:14:24: error: expected primary-expression before ‘)’ token
14 | return ar.get<val>();
| ^
main.cc: In instantiation of ‘int idx_ar1(const arr_type&, const idx_t<val>&) [with arr_type = ar_t; long unsigned int val = 1]’:
main.cc:22:12: required from here
main.cc:14:18: error: invalid operands of types ‘<unresolved overloaded function type>’ and ‘long unsigned int’ to binary ‘operator<’
14 | return ar.get<val>();
|
How can I get around this? I require preciesly the behaviour used in the second example. This appears to be a bug in parsing the syntax, or I don't quite have a detailed understanding of the way the member function is being declared.
Try compiling with Clang, too - sometimes it gives better errors than GCC (sometimes worse):
":14:15: error: missing 'template' keyword prior to dependent template name 'get'"

Compilation error in a simple function template [duplicate]

This question already has answers here:
Where and why do I have to put the "template" and "typename" keywords?
(8 answers)
Closed last year.
While experimenting with this stackoverflow answer I encountered a compilation error I don't understand.
With #if 1 the compilation fails with following error log whereas with if 0 the compilation is OK.
Full error log:
Output of x86-64 gcc 11.2 (Compiler #1)
<source>: In function 'void remove(std::vector<T>&, size_t)':
<source>:8:3: error: need 'typename' before 'std::vector<T>::iterator' because 'std::vector<T>' is a dependent scope
8 | std::vector<T>::iterator it = vec.begin();
| ^~~
<source>:8:27: error: expected ';' before 'it'
8 | std::vector<T>::iterator it = vec.begin();
| ^~~
| ;
<source>:9:16: error: 'it' was not declared in this scope; did you mean 'int'?
9 | std::advance(it, pos);
| ^~
| int
<source>: In instantiation of 'void remove(std::vector<T>&, size_t) [with T = int; size_t = long unsigned int]':
<source>:25:9: required from here
<source>:8:19: error: dependent-name 'std::vector<T>::iterator' is parsed as a non-type, but instantiation yields a type
8 | std::vector<T>::iterator it = vec.begin();
| ^~~~~~~~
<source>:8:19: note: say 'typename std::vector<T>::iterator' if a type is meant
Code (available here):
#include <iostream>
#include <vector>
#if 1
template <typename T>
void remove(std::vector<T>& vec, size_t pos)
{
std::vector<T>::iterator it = vec.begin();
std::advance(it, pos);
vec.erase(it);
}
#else
template <typename T>
void remove(std::vector<T>& vec, size_t pos)
{
vec.erase(vec.begin() + pos);
}
#endif
int main()
{
std::vector<int> myvector{ 1,2,3,4 };
remove(myvector, 2);
for (auto element : myvector)
std::cout << ' ' << element;
std::cout << '\n';
return 0;
}
Now if I do what the compiler suggests (typename std::vector<T>::iterator it = vec.begin();) it compiles, but I don't really understand why typename is required here.
The error message says it all:
error: dependent-name 'std::vector<T>::iterator' is parsed as a non-type, but instantiation yields a type
I.e., whilst for you as a programmer it is apparent that std::vector<T>::iterator is a type, for the compiler it is not, and the lack of a leading typename means it parses the dependent-name iterator as a non-type, but when instantiating the function template and thus its blueprinted definition for T as int, std::vector<T>::iterator is resolved as the (member alias declaration) type std::vector<int>::iterator.
Whilst P0634R3 (Down with typename!), introduced for C++20:
[...] removes the need to disambiguate a dependent name as a typename via the typename keyword from several places where this is already unambiguous
the example above is not such a place/context. To understand why the compiler cannot resolve this unambiguously for all T's, see the example in the end of this answer.
If anything this is a compilation error resulting from a verbose approach to the function's definition. There is no need to include a dependent name in the declaration of the iterator variable:
void remove(std::vector<T>& vec, size_t pos)
{
auto it = vec.begin();
std::advance(it, pos);
vec.erase(it);
}
template<typename T>
struct Evil {
using iterator = T*;
};
template<>
struct Evil<int> {
static constexpr int iterator{42};
};
template<typename T>
void f() {
static_cast<void>(Evil<T>::iterator);
}
int main() {
f<int>(); // well-formed by evil explicit specialization
f<char>(); // ill-formed by primary template where `iterator` is a type
// error: missing 'typename' prior to dependent type name 'Evil<char>::iterator'
}
Or, courtesy of #Jarod42, even more evil where each case might work but do entirely different things.
template <bool isType>
struct Evil {
using var = int;
};
template<>
struct Evil<false> {
static constexpr int var {42};
};
template<bool isType>
void f()
{
[[maybe_unused]]int b = 42;
if constexpr (isType)
{
[[maybe_unused]] typename
Evil<isType>::var * b; // is it a pointer declaration shadowing b
// or a multiplication
} else {
Evil<isType>::var * b; // is it a pointer declaration shadowing b
// or a multiplication
}
}
int main()
{
f<true>();
f<false>();
}
DEMO.

Constraints not satisfied for template template concept requiring static template method

I'm trying to implement Functor and various other category-theoretic concepts using C++ concepts, but am getting compile errors:
http://coliru.stacked-crooked.com/a/e8b6eb387229bddf
Here's my full code (I know that requiring fmap<int, int> does not verify fmap for any two types, and I plan to change it to fmap<int, std::string> or something to achieve a slightly stronger test -- or instead, possibly alter the Functor concept so that it takes in addition to F, two types T and U and verifies the existence of fmap<T, U>, but that's all after I figure out how to fix the error that I'm getting):
#include <functional>
#include <iostream>
#include <vector>
// empty Functor_Impl struct - specialize for each functor
template<template<class> class F> struct Functor_Impl {};
// std::vector Functor implementation
template<>
struct Functor_Impl<std::vector> {
template<class T, class U>
static std::vector<U> fmap(std::vector<T> x, std::function<U(T)> f) {
std::vector<U> out;
out.reserve(x.size());
for (int i = 0; i < x.size(); i++) {
out.push_back(f(x[i]));
}
return out;
}
};
// Functor concept requires Functor_Impl<F> to have fmap
template<template<class> class F>
concept bool Functor = requires(F<int> x) {
{Functor_Impl<F>::template fmap<int, int>(x)} -> F<int>;
};
// Test function using constraint.
template<template<class> class F, class T>
requires Functor<F>
F<T> mult_by_2(F<T> a) {
return Functor_Impl<F>::template fmap<T, T>(a, [](T x) {
return x * 2;
});
}
int main() {
std::vector<int> x = {1, 2, 3};
std::vector<int> x2 = mult_by_2(x);
for (int i = 0; i < x2.size(); i++) {
std::cout << x2[i] << std::endl;
}
}
And the compile error:
lol#foldingmachinebox:~/p/website-editor$ g++ foo.cpp -std=c++17 -fconcepts -o foo
foo.cpp: In function ‘int main()’:
foo.cpp:39:38: error: cannot call function ‘F<T> mult_by_2(F<T>) [with F = std::vector; T = int]’
std::vector<int> x2 = mult_by_2(x);
^
foo.cpp:31:6: note: constraints not satisfied
F<T> mult_by_2(F<T> a) {
^~~~~~~~~
foo.cpp:24:14: note: within ‘template<template<class> class F> concept const bool Functor<F> [with F = std::vector]’
concept bool Functor = requires(F<int> x) {
^~~~~~~
foo.cpp:24:14: note: with ‘std::vector<int> x’
foo.cpp:24:14: note: the required expression ‘Functor_Impl<F>::fmap<int, int>(x)’ would be ill-formed
I'm guessing that my syntax for the concept itself is wrong - that it's treating a variable as a function, or vice versa, since I'm not very familiar with the concept syntax, and in addition some of the example code on cppreference.com does not compile under GCC's implementation (e.g. concept EqualityComparable does not compile, it must be changed to concept bool EqualityComparable).
If I remove requires Functor<F> from the mult_by_2 function declaration, then the code compiles and runs.
The problem is exactly what the error message says: Functor_Impl<F>::template fmap<int, int>(x) is not a valid expression. Functor_Impl<std::vector>::fmap has two parameters, not one.

template specialization for std::atomic<double> &

I have this MCVE:
#include <stdio.h>
#include <atomic>
template<typename T> void assertVariableHasBeenSet( T, const char * );
template<> void assertVariableHasBeenSet<std::atomic<double> &>
( std::atomic<double> & myDouble,
const char * variableName
)
{
printf( "Double:%s=%f\n", variableName, myDouble.load() );
};
int main()
{
std::atomic<double> myDoubleAtomic {23.45};
assertVariableHasBeenSet( myDoubleAtomic, "myDoubleAtomic" );
}
I get this compiler error:
getType.cpp: In function ‘int main()’:
getType.cpp:14:61: error: use of deleted function ‘std::atomic<_Tp>::atomic(const std::atomic<_Tp>&) [with _Tp = double]’
assertVariableHasBeenSet( myDoubleAtomic, "myDoubleAtomic" );
^
In file included from getType.cpp:2:0:
/usr/local/include/c++/4.9.4/atomic:169:7: note: declared here
atomic(const atomic&) = delete;
^
getType.cpp:4:27: error: initializing argument 1 of ‘void assertVariableHasBeenSet(T, const char*) [with T = std::atomic<double>]’
How can I pass a std::atomic<double> reference to the specialized template?
In a normal function it is possible.
For this case, T will be deduced as std::atomic<double>, not std::atomic<double> &. Then the primary template will always be invoked instead of the specialization.
You can specify the template argument explicitly, e.g.
assertVariableHasBeenSet<std::atomic<double> &>(myDoubleAtomic, "myDoubleAtomic");
Or apply overloading.
template<typename T> void assertVariableHasBeenSet( T, const char * );
void assertVariableHasBeenSet( std::atomic<double> & myDouble,
const char * variableName
)
{
printf( "Double:%s=%f\n", variableName, myDouble.load() );
}
Your issue is here:
template<typename T> void assertVariableHasBeenSet( T, const char * );
The primary template will be chosen because myDoubleAtomic is of type std::atomic<double>, not std::atomic<double> &.
The primary template tries to pass T by value, requiring a copy. std::atomic has a deleted copy constructor resulting in that error.
You should tell the compiler what type to use explicitly :
assertVariableHasBeenSet<std::atomic<double> &>(myDoubleAtomic, "myDoubleAtomic" );
The first thing to happen is overload resolution. During overload resolution the type T is deduced as std::atomic<double>. Next the proper specialisation is determined. There is no specialised version and the primary template is used. The specialisation for std::atomic<double>& will never be found by deduction.
There are two approaches to fix the problem (I don’t consider specifying the type explicitly a solution):
Declare the primary template to take a forwarding reference T&& as this would deduce T as std::atomic<double>&.
Instead of template specialisation use overloading, i.e., remove the template<> and the <std::atomic<double>&> after the function name.

Simple constexpr function failed to compile with GCC (clang is OK)

The following code does not compile with GCC 5.2 (C++14). It does compile with clang 3.6 (C++14). (original code can be found here)
#include <cstddef>
#include <algorithm>
#include <type_traits>
#include <utility>
template <typename T>
class aggregate_wrapper;
template <typename T, std::size_t n>
class aggregate_wrapper<T[n]> {
public:
using array = T[n];
template <typename... Ts, typename = decltype(array{std::declval<Ts>()...})>
aggregate_wrapper(Ts&&... xs)
: arr_{std::forward<Ts>(xs)...} {
// nop
}
aggregate_wrapper(const array& arr) {
std::copy(arr, arr + n, arr_);
}
aggregate_wrapper(array&& arr) {
std::move(arr, arr + n, arr_);
}
operator T* () {
return arr_;
}
operator const T* () const {
return arr_;
}
constexpr std::size_t size() const {
return n;
}
private:
array arr_;
};
int main() {
aggregate_wrapper<int[3]> arr;
static_assert(arr.size() == 3, "");
}
The error message produced is
main.cpp: In function 'int main()':
main.cpp:44:3: error: non-constant condition for static assertion
static_assert(arr.size() == 3, "");
^
main.cpp:44:25: error: call to non-constexpr function 'constexpr std::size_t aggregate_wrapper<T [n]>::size() const [with T = int; long unsigned int n = 3ul; std::size_t = long unsigned int]'
static_assert(arr.size() == 3, "");
^
main.cpp:34:25: note: 'constexpr std::size_t aggregate_wrapper<T [n]>::size() const [with T = int; long unsigned int n = 3ul; std::size_t = long unsigned int]' is not usable as a constexpr function because:
constexpr std::size_t size() const {
^
main.cpp:34:25: error: enclosing class of constexpr non-static member function 'constexpr std::size_t aggregate_wrapper<T [n]>::size() const [with T = int; long unsigned int n = 3ul; std::size_t = long unsigned int]' is not a literal type
main.cpp:10:7: note: 'aggregate_wrapper<int [3]>' is not literal because:
class aggregate_wrapper<T[n]> {
^
main.cpp:10:7: note: 'aggregate_wrapper<int [3]>' is not an aggregate, does not have a trivial default constructor, and has no constexpr constructor that is not a copy or move constructor
Any ideas? Should the code compile according to the standard?
Or you could just make your existing variadic constructor serve as your constexpr constructor to perform the default construction:
template <typename... Ts, typename = decltype(array{std::declval<Ts>()...})>
constexpr // <---- ADD THIS
aggregate_wrapper(Ts&&... xs)
: arr_{std::forward<Ts>(xs)...} {
// nop
}
In order for g++ to get it compiled you will need to add a default constructor:
aggregate_wrapper() = default;
please see it in action at: http://coliru.stacked-crooked.com/a/df1ac057960bebc7
I have the feeling that clang under the hood added it, but I am not 100% sure ...
GCC is wrong. Its diagnostic, in part, says:
main.cpp:34:25: note: '<...>' is not usable as a constexpr function because:
main.cpp:34:25: error: enclosing class of constexpr non-static member function '<...>' is not a literal type
... but there is no such rule. See [dcl.constexpr]/3 for the list of constraints that apply here.
You can work around the bogus GCC diagnostic by adding a dummy constexpr constructor (it's fine for that constructor to be private and/or deleted if you don't want any of your real constructors to be constexpr) or by making size be static.