Template for non-builtins, overload for builtins - c++

I am providing a library that supports a function bar(). What it does when you pass in a scalar value (like a double, int, whatever) is different from what happens if you pass in something that is not a scalar value (in all expected cases, a user-defined type). So I wrote code like this:
#include <iostream>
class Foo
{
public:
template <class T> void bar(T const &rhs) { std::cout << "T" << std::endl; }
void bar(double rhs) { std::cout << "double" << std::endl; }
};
int main()
{
Foo foo;
foo.bar(4);
}
The problem with this is on the second line of main(). The result of this code is output of "T". The compiler prefers the template over the call to bar(double), and I am assuming this is because the parameter is an int, which it would rather cast to int const& (since a const& can reference an r-value).
My question is "is there a way I can support every scalar value without explicitly calling them out?" I really don't want to call out every possible type, because... well... there's a lot of them. I would have to cover everything from char to long long, include every combination of volatile and unsigned, and so forth.
I know that just changing the 4 to a 4.0 works, but this is for the public interface to a library, and requiring the user to type 4.0 instead of 4 is just dirty.

Yes, with traits:
#include <type_traits>
#include <iostream>
class Foo
{
public:
template <class T>
typename std::enable_if<!std::is_scalar<T>::value, void>::type bar(T const & rhs)
{
std::cout << "T" << std::endl;
}
void bar(double rhs)
{
std::cout << "double" << std::endl;
}
};
There are six basic categories of types: scalars, functions, arrays, classes, unions and references. And void. Each of them have a corresponding trait. See here for more details.

Related

C++ different using declarations for different concepts

Let's say, I have my List<T> class. I have a lot of functions where I have to pass a single object of my T type. For instance
void add(const T& item)
{
...
}
and it makes sense if T is some class or a struct. However, if T is a byte or integer, it's pointless or even wrong to pas it via reference, since memory pointer costs 8 bytes (4 on 32 bit system), i.e. I pass 1 byte size data type via 8 byte size pointer.
And so I decided to define argument data type using using directive. Kind of:
using argType = const T&; requires sizeof(T) > 8
using argType = T; requires sizeof(T) <= 8
But, obviously, this code doesn't work. Can you, please, propose me other solutions for that?
It sounds like what you need is conditional_t:
#include <type_traits>
template<class T>
class List {
using argType = std::conditional_t<(sizeof(T) > 8), const T&, T>;
void add(argType item) { }
};
To add to the answer provided here: https://stackoverflow.com/a/69864339/2963099 where conditional_t was suggested
It is important to make sure that items with non-trivial copy constructors are passed by reference, since a value copy can be very expensive. As such I would limit pass by value to trivially copyable objects.
Also using sizeof(T*) will work more generically than a hardcoded 8
Note that args are reversed and the test is to use pass by value, and objects with sizeof == 8 can still be pass by reference if needed.
using argType = std::conditional_t<
(sizeof(T) <= sizeof(T *)) && std::is_trivially_copyable_v<T>,
T, const T&>;
An example of usage:
#include <type_traits>
#include <vector>
#include <iostream>
struct Test
{
Test() : x{0}
{
std::cout << "Default construct" << std::endl;
}
Test(const Test& rhs) : x{rhs.x}
{
std::cout << "copy: " << x << std::endl;
}
int * x;
};
template<class T>
struct List {
using argType = std::conditional_t<(sizeof(T) <= sizeof(T *)) && std::is_trivially_copyable_v<T>, T, const T&>;
static argType copy(argType item)
{
std::cout << "Running Copy" << std::endl;
return item;
}
};
auto foo(typename List<Test>::argType t)
{
return List<Test>::copy(t);
}
auto goo(typename List<unsigned long long>::argType t)
{
return List<unsigned long long>::copy(t);
}
int main()
{
Test t;
std::cout << "Calling Test Copy" << std::endl;
foo(t);
std::cout << "After Test Copy" << std::endl;
unsigned long long u;
std::cout << "Calling ULL Copy" << std::endl;
goo(u);
std::cout << "After ULL Copy" << std::endl;
}
see it here: https://godbolt.org/z/WTKT8G6rh
Note, you can see the parameter of foo() and goo() easily here
Note how Test would have been passed by value and incurred a possibly expensive copy, but checking for an expensive copy prevents this
With c++20 concepts you can add some constraints to your templates,
from en.cppreference.com/w/cpp/language/constraints
Class templates, function templates, and non-template functions (typically members of class templates) may be associated with a constraint, which specifies the requirements on template arguments, which can be used to select the most appropriate function overloads and template specializations.
Named sets of such requirements are called concepts. Each concept is a predicate, evaluated at compile time, and becomes a part of the interface of a template where it is used as a constraint:
#include <string>
#include <cstddef>
#include <concepts>
// Declaration of the concept "Hashable", which is satisfied by any type 'T'
// such that for values 'a' of type 'T', the expression std::hash<T>{}(a)
// compiles and its result is convertible to std::size_t
template<typename T>
concept Hashable = requires(T a) {
{ std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
};
struct meow {};
// Constrained C++20 function template:
template<Hashable T>
void f(T) {}
//
// Alternative ways to apply the same constraint:
// template<typename T>
// requires Hashable<T>
// void f(T) {}
//
// template<typename T>
// void f(T) requires Hashable<T> {}
int main() {
using std::operator""s;
f("abc"s); // OK, std::string satisfies Hashable
//f(meow{}); // Error: meow does not satisfy Hashable
}
In your case I think you can constraint your template as follows:
template<typename T>
concept InfTo8Bytes = requires(T a) {
sizeof (T) <= 8;
};
and use you concepts as follows:
template<InfTo8Bytes T>
class Foo {
};

Typename Keyword for SFINAE [duplicate]

This question already has answers here:
Officially, what is typename for?
(8 answers)
Closed 3 years ago.
I was studying about SFINAE in modern C++ which I see the following code:
#include <iostream>
struct Bar
{
typedef double it;
};
template <typename T>
typename T::it Foo(const T& arg_f) {
std::cout << "Foo<T>" << std::endl;
return 0;
}
int Foo(int i) { std::cout << "foo(int)" << std::endl; return 0; }
int main(int argc, const char* argv[])
{
Foo(Bar());
Foo(0);
return 0;
}
Why in this code, the developer used typename T::it?
How that typename is related to structure of Bar? because it variable is just defined in bar struct but it used outside of struct for function declaration.
What is SFINAE at all?
The keyword typename is used here because you are accessing a type member of a template type argument.
It is completely unrelated except for the fact that if T is Bar then it should expose a it type member to gain access to the overload.
Substitution Failure Is Not An Error is a template meta programming pattern that relies on "removing" overloads that could not compile
In the template function you put here, the developer indirectly specifies Foo is just a function to work with Bar structure (or its derivated instances). So if you instantiated it like Foo(Bar()), the template function deduced by compiler like the following:
Bar::it Foo(const Bar& arg_f) {
std::cout << "Foo<T>" << std::endl;
return 0;
}
But if we pass an integer value rather Bar object to the function, it will be instantiated like the following codes:
int::it Foo(const int& arg_f) {
std::cout << "Foo<T>" << std::endl;
return 0;
}
Which has a wrong implementation and as a result, the compiler will fail because int class has not it member.
However, if you want to handle this issue, you should overload foo function for int values like the following:
int Foo(int arg_f)
{
std::cout << "Foo<int>" << std::endl;
return arg_f;
}
Or you can use enable_if_t for enabling a template function for a specialized data type like floating-point or ...:
template <typename T>
typename std::enable_if_t<std::is_floating_point<T>::value, T> Foo(T t)
{
std::cout << "Foo<floating point>" << std::endl;
return t;
}
Also, I should clarify type name just make a difference between a value and a type. when you use it, the compiler treats that object as a type, not a value because of that, the developer uses it to make compiler aware it is a type, not a value.
Also, as #Vivick said, Substitution Failure Is Not An Error is a template metaprogramming pattern that relies on "removing" overloads that could not compile. However, Wikipedia has a good reference for SFINAE: https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error

Passing by value all types but string to a template function

I want to define a template function that gets one argument passed by value for all types but std::string (and const char*).
template<typename T>
void foo( T value )
{
// some code using value
}
The std::string version should behave exactly as the template version, but have its parameter passed by const&.
What is the best approach to do what I want without duplicating the body of foo()?
The best I was able to think is to wrap the code using value inside another function, and then call it inside all versions of foo() (the template version and the std::string overload). Is there another way? For example, is it possible to call the template version from within the std::string overload?
EDIT
What I want to know is a good rule of thumb for avoiding code duplication among various specializations and overloads. What is a good pattern to follow? Shall I define a wrapper function for the body and then call that from within all overloads/specializations, or there is another way?
In order to avoid code duplication, the answer by 101010 can be extended to actually call the template from within the overload:
#include <string>
#include <iostream>
#include <type_traits>
#include <boost/core/demangle.hpp>
template<typename T>
void foo( T value )
{
std::cout << "inside template" << std::endl;
std::cout << boost::core::demangle(typeid(value).name()) << std::endl;
}
void foo(const std::string &value)
{
std::cout << "inside const string overload" << std::endl;
foo<const std::string&>(value);
}
int main()
{
foo(10);
foo(std::string("hello"));
return 0;
}
output
inside template
int
inside const string overload
inside template
std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >
live example
Simple solution: provide an overload for std::string:
void foo( std::string const &value ) {
// some code using value
}
I think what you are looking for is rvalue signature in C++ 11.
Its as simple as:
#include <iostream>
#include <string>
template<typename T>
void foo(T&& value)
{
std::cout << "was passed by refernece:" << std::is_lvalue_reference<T&&>::value << std::endl;
std::cout << value << std::endl;
}
int main()
{
std::string text = "hello";
foo(text);
foo(1);
}
You can either pass the parameter by reference or by value and the rvalue rules will use the appropriate type.
You can define a type-trait-like class that will convert std::string to std::string& and will keep the type for all other types:
template<class T>
struct helper {
typedef T type;
};
template<>
struct helper<std::string> {
typedef std::string& type; // or const std::string& type if you want
};
template<typename T>
void foo( typename helper<T>::type value, T value2 )
{
value = value2;
}
int main()
{
int a = 10;
foo(a, 42);
std::cout << a << std::endl; // prints 10
std::string s = "abc";
foo(s, std::string("def"));
std::cout << s << std::endl; // prints def
}
Full example: http://coliru.stacked-crooked.com/a/96cf78e6c4846172
UPD: as noted by #PiotrSkotnicki, having only one parameter makes type-deduction fail. However, I will keep the answer as it might be helpful in case you indeed have several parameters of type T or if you are ok with specifying explicit template parameter to foo.
UPD2: To solve the type-deduction problem, you may add another wrapper:
template<typename T>
void foo_helper( typename helper<T>::type value )
{
value = T();
}
template<typename T>
void foo(T& value)
{
foo_helper<T>(value);
}
This still might have some problems, so whether this is applicable to your usecase, is up to you to decide.
use std::enable_if + std::is_convertibale:
template<typename T>
typename std::enable_if<!std::is_convertible<T,std::string>::value>::type foo( T value )
{
// some code using value
}

Prohibition of operator << calling

I have some code.
#include <iostream>
template<typename T>
struct Test
{
Test(bool v):flg(v) { }
void func() { }
typedef void (Test::*unspecified)();
operator unspecified() const
{
return flg ? &Test::func : 0;
}
bool flg;
};
template<typename T>
std::ostream& operator << (std::ostream&, typename Test<T>::unspecified);
int main()
{
Test<int> t(true);
std::cout << t << std::endl;
}
Output is
1
It works fine, but i want to get undefined reference. If Test is not template class i get undefined reference. So, why compiler not use operator << for function type and do standart conversion from pointer to class-member to bool?
In typename Test<T>::unspecified, T is in a non-deducible context, since it appears to the left of a ::. Thus your function template is never even considered, and the conversion to unspecified is used as the sole viable overload.
The short answer is simply that "templates don't work like that". Let me know if you want a longer answer.

Template Specialization for basic POD only

Is there a subtle trick for template specialization so that I can apply one specialization to basic POD (when I say basic POD I don't particularly want struct POD (but I will take that)).
template<typename T>
struct DoStuff
{
void operator()() { std::cout << "Generic\n";}
};
template<>
struct DoStuff</*SOme Magic*/>
{
void operator()() { std::cout << "POD Type\n";}
};
Or do I have to write specializations for each of the built in types?
template<typename T>
struct DoStuff
{
void operator()() { std::cout << "Generic\n";}
};
// Repeat the following template for each of
// unsigned long long, unsigned long, unsigned int, unsigned short, unsigned char
// long long, long, int, short, signed char
// long double, double, float, bool
// Did I forget anything?
//
// Is char covered by unsigned/signed char or do I need a specialization for that?
template<>
struct DoStuff<int>
{
void operator()() { std::cout << "POD Type\n";}
};
Unit Test.
int main()
{
DoStuff<int> intStuff;
intStuff(); // Print POD Type
DoStuff<std::string> strStuff;
strStuff(); // Print Generic
}
If you really want only fundamental types and not user-defined POD types then the following should work:
#include <iostream>
#include <boost/type_traits/integral_constant.hpp>
#include <boost/type_traits/is_fundamental.hpp>
#include <boost/type_traits/is_same.hpp>
template<typename T>
struct non_void_fundamental : boost::integral_constant<
bool,
boost::is_fundamental<T>::value && !boost::is_same<T, void>::value
>
{ };
template<typename T, bool Enable = non_void_fundamental<T>::value>
struct DoStuff
{
void operator ()() { std::cout << "Generic\n"; } const
};
template<>
struct DoStuff<T, true>
{
void operator ()() { std::cout << "POD Type\n"; } const
};
If you also want user-defined POD types, then use boost::is_pod<> instead of non_void_fundamental<> (and if you're using C++11 and doing this for optimization purposes, use std::is_trivially_copyable<> instead).
In C++11, many traits have been added to the standard library, and most seem particularly aimed toward interesting specializations (and notably bitwise manipulations).
The top-level trait you could be interested in is std::is_trivial, however there are many others:
std::is_trivially_default_constructible
std::is_trivially_copy_constructible
std::is_trivially_move_constructible
std::is_trivially_copyable (can be copied via memcpy)
In general, the Standard has tried to get as finer grained traits as possible so you need not rely on such broad assumptions as is_pod but instead fine-tune your constraints to match what your methods really need.
Boost has boost::is_pod. Is that what you're looking for?
(I've never used it, so I won't embarrass myself by trying to formulate the precise code that you require for your example.)