Function template specialization in C++, no Instance of overloaded function - c++

I'm learning about function template specialization in C++ and am tasked with writing a template function called plus that returns the sum of it's two arguments which maybe of different types. One version that accepts by value and another by pointer. As an added challenge, I'm asked to overload this function so that it concatenates two strings.
template <typename T1, typename T2> decltype(auto) plus(const T1& a, const T2& b) {
return a + b;
}
template <typename T1, typename T2> decltype(auto) plus(const T1* a, const T2* b) {
return *a + *b;
}
// concatenate two strings
template <>
std::string_view plus<std::string_view, std::string_view> (std::string_view a, std::string_view b) {
return std::string { a } + std::string{ b };
}
The problem is that I'm getting an error on the specialization overload of the function to concatenate two strings. The reason I decided to choose std::string_view over std::string is so that when calling the function with string literals (const char*) it wouldn't resolve to the second definition that accepts a const * which I'm guessing would be resolved over std::string.
So I can't really figure out what's going on. Just a wild guess but maybe this has something to do with me having two different template functions called plus and it can't figure out which one I'm trying to specialize / overload?
UPDATE:
The issue seems to be with template resolution. The definition that accepts const T* is always preferred for any string literals. Just trying to find a fix.

This would be my suggestion:
template <typename T1, typename T2, typename T3> T3 plus(const T1& a, const T2& b) {
return a + b;
}
template <typename T1, typename T2, typename T3> T3 plus(const T1* a, const T2* b) {
return *a + *b;
}
template <typename T1, typename T2, typename T3> T3 plus(T1 a, T2 b) {
return a + b;
}
// concatenate two strings
template <>
std::string plus<std::string_view, std::string_view> (std::string_view a, std::string_view b) {
return std::string(a).append(b);
}
Since string views needs to refer to the content of another string you need to return a string since the newly created string_view would point to a temporary object.
Also there is no way to concatenate 2 string_view's together since concatenating two strings together would require that string_view's are able to hold references to other string views (since they don't hold string content themselves).
Furthermore, a third typename is required since this implementation would return another type (std::string) since you don't want to return a string_view of a temporary

If you have access to C++20, then this could be done pretty easily with concepts.
Anything that's convertible to string_view, such as string and const char*, will be taken to this function:
template<typename T>
concept StringView = std::convertible_to<T, std::string_view>;
auto plus(StringView auto a, StringView auto b)
{
return std::string(a).append(b);
}
Similarly, you can define other concepts easily and just exlucde StringView:
template<typename T>
concept Reference = std::is_reference_v<T> && !StringView<T>;
template<typename T>
concept Pointer = std::is_pointer_v<T> && !StringView<T>;
auto plus(const Reference auto a, const Reference auto b)
{
⋮
⋮

Related

C++ concept for compound assignment operators

I have a concept for normal binary operators
template<typename Op, typename T> concept is_binary_operation =
requires (const T& t1, const T& t2) // e.g. a+b
{
{Op()(t1,t2)}->std::convertible_to<T>;
};
and a concept for compound assignment operators
template<typename Op, typename T> concept is_operation_and_assign =
requires (T& t1, const T& t2) // e.g a += b;
{
{Op()(t1,t2)}->std::convertible_to<T>;
};
For compound assignment operators this works as expected:
template<typename T> struct op_and_assign
{
T& operator()(T& t1, const T& t2)
{
t1 += t2;
return t1;
}
};
This "is_operation_and_assign" but not "is_binary_operation"
std::cout << is_binary_operation<op_and_assign<double>, double> << " ";
std::cout << is_operation_and_assign<op_and_assign<double>, double> << std::endl;
prints "0 1". std::plus, however, satisfies both concepts:
std::cout << is_binary_operation<std::plus<double>, double> << " ";
std::cout << is_operation_and_assign<std::plus<double>, double> << std::endl;
prints "1 1".
How do I have to change the concept "is_operation_and_assign" so that I get the output "1 0", i.e. so that it will fulfilled by op_and_assign but not by std::plus?
To make more clear what I need: I have two versions of an algorithm, one using the compound assignment operator, one using the binary operator:
template<typename Op, typename T>
int f() requires is_operation_and_assign<Op, T>
{
return 0;
}
template<typename Op, typename T>
int f() requires is_binary_operation<Op, T>
{
return 1;
}
I can call the version for op_and_assign
f<op_and_assign<double>, double>();
but the version for std::plus
f<std::plus<double>, double>();
does not compile. (error: call to 'f' is ambiguous)
Update: in the meanwhile I found a workaround:
When I simply add && !is_binary_operation<Op, T> to the first f:
template<typename Op, typename T>
int f() requires (is_operation_and_assign<Op, T>
&& !is_binary_operation<Op, T>)
{
return 0;
}
template<typename Op, typename T>
int f() requires is_binary_operation<Op, T>
{
return 1;
}
then the second call is no longer ambiguous, i.e. both
f<op_and_assign<double>, double>();
f<std::plus<double>, double>();
compile (and choose the desired function).
It's important to clarify what actually your concept is checking, because it's not what you think it is.
This:
template<typename Op, typename T> concept is_operation_and_assign =
requires (T& t1, const T& t2) // e.g a += b;
{
{Op()(t1,t2)}->std::convertible_to<T>;
};
Checks that you can invoke Op()(t1, t2) with a T& and a T const& and that you get something that satisfies convertible_to<T>. When you provide:
template<typename T> struct op_and_assign
{
T& operator()(T& t1, const T& t2)
{
t1 += t2;
return t1;
}
};
as the first template parameter, what does that actually check? This is an unevaluated expression, we're checking to see if we can invoke op_and_assign<T>(). We're not evaluating the body of the call operator, we're just checking to see if it's a valid call. So it's no different than if we wrote:
template<typename T> struct op_and_assign
{
T& operator()(T& t1, const T& t2);
};
It's unevaluated, there's no body, so the only things that matter are constraints. Here, there are no constraints, so op_and_assign is always invokable as long as the arguments are convertible.
When you do this:
is_binary_operation<op_and_assign<double>, double>
You're effectively asking if you can convert the arguments appropriately. For is_binary_operation, you're providing two arguments of type double const& (from your requires expression) but op_and_assign<double> needs to take one double&. That's why this particular check doesn't work.
For how to fix it. op_and_assign which should probably look something like this:
struct op_and_assign
{
template <typename T, typename U>
auto operator()(T&& t, U&& u) const -> decltype(t += u);
};
Now we're actually checking if we can perform +=.
But that won't change that you can't assign to a double const&. You're getting the correct answer there, even if you weren't doing the check you intended to.

Combine multiple vectors (results of function) into one with template

I'd like to have a templated function taking in a vector<T> v and a function op, mapping T to vector<U> and would like to concatenate the results of applying f to every element vector of v to return a vector<U> = [ Elements of op(v[0]), Elements of op(v[1]) ...].
A working option I found was adding an example in the function to allow for template deduction:
template <typename Container>
Container& concat(Container& c1, Container const& c2) {
c1.insert(end(c1), begin(c2), end(c2));
return c1;
}
template <typename Container, typename UnaryOperation, typename U>
inline auto to_vec_from_vectors(Container& c, UnaryOperation&& op, U& ex)
-> std::vector<U> {
std::vector<U> v;
for (auto& e : c) {
std::vector<U> opv = op(e);
concat(v, opv);
}
return v;
}
But naturally I'd like to produce the same result with only the two parameters.
My attempt [replacing U with decltype(*std::begin(op(*std::begin(c))))]:
template <typename Container, typename UnaryOperation, typename U>
inline auto to_vec_from_vectors(Container& c, UnaryOperation&& op, U& ex)
-> std::vector<decltype(*std::begin(op(*std::begin(c))))> {
std::vector<decltype(*std::begin(op(*std::begin(c))))> v;
for (auto& e : c) {
std::vector<decltype(*std::begin(op(*std::begin(c))))> opv = op(e);
concat(v, opv);
}
return v;
}
Unfortunately this didn't compile. I'm also worried of wasting time if op is complex method.
This gave:
error: conversion from ‘std::vector<U>’ to non-scalar type ‘std::vector<const U&, std::allocator<const U&> >’ requested
error: forming pointer to reference type ‘const U&
...
so it seems to be related to 'const'.
How would this variant be corrected? Are there better alternatives?
Dereferencing a container iterator yields a reference (or a const reference, if the container was const), which is why decltype(*std::begin(op(*std::begin(c)))) yields const U& according to your compiler error (and not U).
You can fix this by either removing the reference again with std::remove_reference (or, if you want to also remove const and volatile, std::remove_cvref), or by just asking the vector for what it actually stores:
decltype(*std::begin(op(*std::begin(c)))) -> typename decltype(op(*std::begin(c)))::value_type
I have gone ahead and removed the unneeded U& ex parameter.
template <typename Container, typename UnaryOperation>
inline auto to_vec_from_vectors(Container& c, UnaryOperation&& op)
-> std::vector<typename decltype(op(*std::begin(c)))::value_type> {
std::vector<typename decltype(op(*std::begin(c)))::value_type> v;
for (auto& e : c) {
std::vector<typename decltype(op(*std::begin(c)))::value_type> opv = op(e);
concat(v, opv);
}
return v;
}
Demo
You can also avoid the triple repetition of the decltype chant by naming it:
template <typename Container, typename UnaryOperation>
using applied_op_t = typename decltype(std::declval<UnaryOperation>()(*std::begin(std::declval<Container>())))::value_type;

General min and max - C++

Writing a general minimum function, Two questions came to my mind. The code works fine with any input type and different argument number:
namespace xyz
{
template <typename T1, typename T2>
auto min(const T1 &a, const T2 &b) -> decltype(a+b)
{
return a < b ? a : b;
}
template <typename T1, typename T2, typename ... Args>
auto min(const T1 &a, const T2 &b, Args ... args) -> decltype(a+b)
{
return min(min(a, b), args...);
}
}
int main()
{
cout << xyz::min(4, 5.8f, 3, 1.8, 3, 1.1, 9) << endl;
// ^ ^ ^
// | | |
// float double int
}
Is there a better replacement for decltype(a+b)? I thing there's a standard class which I can't remember, something like decltype(std::THE_RESULT<a,b>::type).
The returned type of that decltype(std::THE_RESULT<a,b>::type)is const & or not ?
std::common_type(c++11):
For non-specialized std::common_type, the rules for determining the
common type between every pair T1, T2 are exactly the rules for
determining the return type of the ternary conditional operator where
T1 and T2 are the types of its second and the third operands.
and
For arithmetic types, the common type may also be viewed as the type
of the (possibly mixed-mode) arithmetic expression such as T0() + T1()
+ ... + Tn().
Not sure about const&, but you could play with std::remove_cv and std::remove_reference (and std::is_reference to find out the answer).
In fact, here's a list of type support utilities. Knock yourself out.
After the answer and worth comments I did it as below:
template <typename T1, typename T2>
auto min(const T1 &a, const T2 &b)
-> typename std::common_type<const T1&, const T2&>::type
{
return a < b ? a : b;
}
template <typename T1, typename T2, typename ... Args>
auto min(const T1 &a, const T2 &b, const Args& ... args)
-> typename std::common_type<const T1&, const T2&, const Args& ...>::type
{
return min(min(a, b), args...);
}

Making a tuple hold either a const reference or a const value

I'm trying to make a std::tuple that ends up holding either const references, or a value that was either copied or moved as appropriate where taking a reference wouldn't be sensible (e.g. temporaries).
So far I've got:
#include <functional>
#include <iostream>
template <typename ...Args>
struct foo {
const std::tuple<Args...> values;
};
template <typename T1, typename T2>
foo<T1, T2> make2(T1&& v1, T2&& v2) {
return foo<T1,T2>{std::tuple<T1, T2>(std::forward<T1>(v1),std::forward<T2>(v2))};
}
int main() {
double d1=1000;
double& d2 = d1;
auto f = make2(d2, 0);
std::cout << std::get<0>(f.values) << ", " << std::get<1>(f.values) << "\n";
d1 = -666;
std::get<0>(f.values)=0; // Allowed - how can I inject some more constness into references?
//std::get<1>(f.values) = -1; // Prohibited because values is const
std::cout << std::get<0>(f.values) << ", " << std::get<1>(f.values) << "\n";
}
Which is close, but not quite const enough for what I was hoping - I end up with a const std::tuple<double&, int> which of course allows me to modify the double that the tuple refers to.
I tried sprinkling some more constness into make2:
template <typename T1, typename T2>
foo<T1 const, T2 const> make2(T1&& v1, T2&& v2) {
return foo<T1 const,T2 const>{std::tuple<T1 const, T2 const>(std::forward<T1>(v1),std::forward<T2>(v2))};
}
That succeeded in making the int (i.e. non-reference) tuple member const (not terribly exciting given I can make the whole tuple const easily enough), but did nothing to the double& member. Why? How can I add that extra constness?
It didn't work because T1 const adds top-level const. I.e., it would make double &const, which is not different from double&. You need a inner const: "reference to const T1".
You could build this up with a combination of remove_reference, add_const and add_reference, or just write a small trait that puts the const in the right place:
template <typename T>
struct constify { using type = T; };
// needs a better name
template <typename T>
struct constify<T&> { using type = T const&; };
// and an alias for UX ;)
template <typename T>
using Constify = typename constify<T>::type;

Auto convert const char[] to const char * in template function

I'm having problems with templates whereby if you try and give a templated function a string argument, the compiler interprets "Hello World" as const char [12]. I would like it to be const char *.
I can 'work around' the problem by static casting each string to 'const char*', but since I'm trying to use this as part of a logging system, making it simple is a big goal.
Since its difficult to explain what i mean, i have come up with a simple reproducer. You will see that the last line of the main function doesn't compile.
Any help would be greatly appreciated
#include <string>
// Trivial base class so we can use polymorphism
class StoreItemsBase
{
public:
StoreItemsBase() {}
};
// Example of a trivial Templated class to hold some 3 items.
// Intent to have similar classes to hold 4,5..n items
template <typename T1, typename T2, typename T3>
class Store3Items : public StoreItemsBase
{
public:
Store3Items(const T1& t1, const T2& t2, const T3& t3)
:
StoreItemsBase(),
mT1(t1),
mT2(t2),
mT3(t3)
{}
private:
T1 mT1;
T2 mT2;
T3 mT3;
};
// Function to create a pointer to our object with added id
// There would be similar CreateHolderFunctions for 4,5..n items
template <typename T1, typename T2, typename T3>
StoreItemsBase* CreateHolder(const T1& t1, const T2& t2, const T3& t3)
{
return new Store3Items<T1, T2, T3>(t1, t2, t3);
}
int main()
{
int testInt=3;
double testDouble=23.4;
const std::string testStr("Hello World");
StoreItemsBase* Ok1 = CreateHolder(testInt, testDouble, testStr);
StoreItemsBase* Ok2 = CreateHolder(testDouble, testStr, testInt);
StoreItemsBase* Ok3 = CreateHolder(testStr, static_cast<const char*>("Hello there"), testInt);
// If you try a standard string, it compiler complains
// Although I could surround all my strings with the static cast, what I am looking for is a way
// to for the CreateHolder function to do the work for me
StoreItemsBase* NotOk4 = CreateHolder(testStr, "Hello World", testInt);
// Free our objects not shown in the example
}
Compiler error is:
example.cpp: In constructor ‘Store3Items::Store3Items(const T1&, const T2&, const T3&) [with T1 = std::basic_string, T2 = char [12], T3 = int]’:
example.cpp:50:50: instantiated from ‘StoreItemsBase* CreateHolder(const T1&, const T2&, const T3&) [with T1 = std::basic_string, T2 = char [12], T3 = int]’
example.cpp:65:74: instantiated from here
example.cpp:21:11: error: array used as initializer
You could use a metafunction to transform the types passed as argument to your templates. Any array of chars would be transformed into a char*:
template< typename T > struct transform
{
typedef T type;
};
template< std::size_t N > struct transform< char[N] >
{
typedef char* type;
};
template< std::size_t N > struct transform< const char[N] >
{
typedef const char* type;
};
Then, instead of using Tn directly you would use typename transform< Tn >::type.
Update: If you are working in C++11, then std::decay already does what you want.
Try changing the template arguments to const T1 t1, const T2 t2, const T3 t3. It will be less performant but it compiles
Deciding on function arguments based on template arguments is often difficult. You can try this for the class constructor signature. I used template specialization of the class "arg_type" (non-standard) to make sure all arguments types that are not const pointers are passed by const ref and all const pointers are passed as const pointers.
Also, don't forget the virtual destructor on your base class or bad things might happen :)
#include <string>
// Trivial base class so we can use polymorphism
class StoreItemsBase
{
public:
StoreItemsBase() {}
virtual ~StoreItemsBase() {}
};
template <typename TYPE> class arg_type
{
public:
typedef const TYPE& type;
};
template <typename TYPE> class arg_type<const TYPE*>
{
public:
typedef const TYPE* type;
};
// Example of a trivial Templated class to hold some 3 items.
// Intent to have similar classes to hold 4,5..n items
template <typename T1, typename T2, typename T3>
class Store3Items : public StoreItemsBase
{
typedef typename arg_type<T1>::type arg1;
typedef typename arg_type<T2>::type arg2;
typedef typename arg_type<T3>::type arg3;
public:
Store3Items(arg1 t1, arg2 t2, arg3 t3)
:
StoreItemsBase(),
mT1(t1),
mT2(t2),
mT3(t3)
{}
private:
T1 mT1;
T2 mT2;
T3 mT3;
};
// Function to create a pointer to our object with added id
// There would be similar CreateHolderFunctions for 4,5..n items
template <typename T1, typename T2, typename T3>
StoreItemsBase* CreateHolder(const T1 t1, const T2 t2, const T3 t3)
{
return new Store3Items<T1, T2, T3>(t1, t2, t3);
}
int main()
{
int testInt=3;
double testDouble=23.4;
const std::string testStr("Hello World");
StoreItemsBase* Ok1 = CreateHolder(testInt, testDouble, testStr);
StoreItemsBase* Ok2 = CreateHolder(testDouble, testStr, testInt);
StoreItemsBase* Ok3 = CreateHolder(testStr, static_cast<const char*>("Hello there"), testInt);
// If you try a standard string, it compiler complains
// Although I could surround all my strings with the static cast, what I am looking for is a way
// to for the CreateHolder function to do the work for me
StoreItemsBase* NotOk4 = CreateHolder(testStr, "Hello World", testInt);
// Free our objects not shown in the example
}
Keep in mind, your class will be storing a raw const char* internally (not storing a std::string) so make sure the scope of that string being passed in will be longer lived than the pointer you're storing. Constant strings like in your example are fine because they live forever.