Concept polymorphism in C++ - c++

The short question is: how to make templated function behave differently, based
on parameter concept support.
I mean, if type T implements some concept, then my function should process it specially, than other types, that doesn't.
The question extension is: how this function should be declared, so end-user can see what types it supports in its declaration (user, shouldn't check the defintion in order to obtain this knowledge).
So that's my concrete example, I use the "concepts" idea as it's presented in "The C++ Programming Language" by B. Stroustrup, namely a constexpr predicates, instead of c++20's concepts, since my compiler doesn't support it, yet:
template<typename T>
bool constexpr is_matrix() { ... }
template<size_t N, size_t M, typename T>
class Matrix {
...
template<typename U>
Matrix& operator+=(const U& v) {
if constexpr (is_matrix<U>()) {
// handle matrix case
} else {
// handle scalar case
}
}
...
}
This example is taken from my simple Matrix/Vector lib, that I use to study software rendering. My idea here is to require type U to satisfy my concept (support all necessary operations, provide necessary type aliases) instead of requiring it being my Matrix type, when it fails that check it is supposed to be handled as scalar.
So what techniques, can be applied here in order to make this code clearer for end-user, and are there better ways to provide concept-based parametric polymorphism, than constexpr if?
My only solution to this problem, that I was able to come up with was usage of enable_if, like this:
...
template<typename U, typename =
enable_if_t<is_convertible_v<U, T> ||
(is_matrix<U>() && is_convertible_v<matrix_value_type_t<U>, T>)>>
Matrix& operator+=(const U& v) {
...
Which is quite verbose and can't be called pretty, btw I would prefer to static assert that type should be either convertible or being matrix of convertible values instead of hiding this operator.
EDIT: On second thought about my solution, I actually could static_assert in the definition, but still providing assertion criteria in declaration
template<typename U, bool check = is_convertible_v<U, T> || (is_matrix<U>() && is_convertible_v<matrix_value_type_t<U>, T>)>
... {
static_assert(check, "Type U should be either convertible to T, or being a matrix of convertible values");
}
EDIT2: Which further can be improved into more readable variant:
...
template <typename U, bool check = std::disjunction_v<
compatible_type<This, U>,
compatible_matrix<This, U>,
compatible_vector<This, U>>>
Matrix& operator+=(const U& v) {
assert_compatible<check>();
...

Simply overload the operator for all types you want to support. You may also add another version that generates a comprehensible compile-time error
Matrix& operator+=(Matrix const&);
template<typename matrix, typename=enable_if_t<is_matrix<matrix>::value>>
Matrix& operator+=(matrix const&);
template<typename scalar, typename=enable_if_t<is_scalar<scalar>::value>>
Matrix& operator+=(scalar);
template<typename arg>
Matrix& operator+=(arg&&) // catch attempts to add wrong argument type
{
static_assert(is_matrix<arg>::value || is_scalar<arg>::value,
"Matrix::operator+=(arg) requires matrix or scalar argument");
assert(false);
return*this;
}
You may also declare the last operator [[noreturn]].

Related

Check presence of custom overload of function when template overload is available

I am designing an utility header that pumps binary data off an sf::InputStream. For ease of use, is comprises a single function name, readFromStream, that has a lot of (templated and non-templated) overloads for automatically deserializing standard-layout types and type compounds like vectors, tuples and my custom-designed grid class. The complete implementation can be found here: https://github.com/JoaoBaptMG/ReboundTheGame/blob/master/MainGame/utility/streamCommons.hpp
So, I have defined an overload readFromStream that pumps out a vector of any type by calling readFromStream again recursively:
template <typename T, typename std::enable_if<!is_optimization_viable<T>::value, int>::type = 0>
bool readFromStream(sf::InputStream &stream, std::vector<T> &value)
{
size_t size;
if (!readFromStream(stream, VarLength(size)))
return false;
std::vector<T> newVal(size, T());
for (auto &val : newVal)
if (!readFromStream(stream, val))
return false;
newVal.swap(value);
return true;
}
I'd like to write an optimized version for standard-layout classes for that there's not an overload for readFromStream, so we can exploit the memory layout of them and blit them in a single read call:
// trait is_optimization_viable is what I'm having trouble to write
template <typename T, typename std::enable_if<is_optimization_viable<T>::value, int>::type = 0>
bool readFromStream(sf::InputStream &stream, std::vector<T> &value)
{
size_t size;
if (!readFromStream(stream, VarLength(size)))
return false;
std::vector<T> newVal(size, T());
if (stream.read(newVal.data(), size*sizeof(T)) != size*sizeof(T))
return false;
newVal.swap(value);
return true;
}
Well, I could use a solution described on other answers to detect presence of a function, but there's a catch. When the type is standard-layout, I have a default readFromStream that reads like this:
template <typename T, typename std::enable_if<std::is_standard_layout<T>::value, int>::type = 0>
bool readFromStream(sf::InputStream &stream, T& value)
{
return stream.read((void*)&value, sizeof(T)) == sizeof(T);
}
So, there's always a function that does the serialization, not just the one I wanted. The problem I want to solve here is: how can I detect the presence of a non-default readFromString for type T, in order to disable the optimized version of readFromString for std::vector<T>?
I have tried to pull a few tricks. I can't limit the optimization to POD types because I'm using sf::Vector2<T> on some types I want to deserialize, which is not POD. I tried to compare the function addresses I get when I use a non-templatized and templatized function, like:
using FPtr = bool(*)(sf::InputStream&, T&);
return (FPtr)readFromStream == (FPtr)readFromStream<T>;
But, strangely enough, it didn't work. And I researched a lot of solutions, but none I could adapt to what I needed. Maybe it's not possible in C++, and I'll have to resort "marking" the types I don't want to be optimized. Or maybe it's some obscure template I haven't thought of. How could I do this?
As I understand it your problem is:
is_optimization_viable<T>;
could be defined by:
template<typename T>
using is_optimization_viable<T> = std::is_standard_layout<T>;
but for the fact that, for certain values of T that are standard layout
you nonetheless require a custom bool readFromStream(sf::InputStream &stream, T &value),
overload which means they are not optimization-viable.
Well as you must write these custom overloads, you know what those
exceptional values of T are. Say they are types X, Y, Z.
Then you can define the trait as:
#include <type_traits>
template<typename T, typename ...Us>
struct is_one_of;
template<typename T>
struct is_one_of<T> {
static constexpr bool value = false;
};
template<typename T, typename First, typename ...Rest>
struct is_one_of<T,First,Rest...> {
static constexpr bool value =
std::is_same<T,First>::value || is_one_of<T,Rest...>::value;
};
// ^ C++17: `std::disjunction` does the job
template<typename T>
using has_custom_read_from_stream = is_one_of<T,X,Y,Z>;
template<typename T>
struct is_optimization_viable {
static constexpr bool value = std::is_standard_layout<T>::value &&
!has_custom_read_from_stream<T>::value;
};
I appreciate that you'd rather avoid the ongoing maintenance of the
hard-coded type-list X, Y, Z, and prefer somehow to SFINAE-probe
whether a call readFromStream(s, t) will be a call to one of the
custom overloads for some std::declval-ed s and t.
But that's a mirage. You tell us, there will be some overload
readFromStream(s, t) that will compile whatever the type of t.
If so, a SFINAE probe will always tell you that Yes, readFromStream(s, t)
will compile - for any T as the unqualified type of t. And you
still have to make a compiletime decision as to whether T is one of
the custom types, and if not, whether it is standard-layout.
That's all there is to the problem. To tell whether T is one of
the custom types you must either test it for identity with any one
of them disjunctively, as shown, or your must find a trait independent of their
identities that is satisfied by all and only the custom types. As you
don't tell us what those custom types are, I can't suggest any such trait,
but if you find one then it will define or replace has_custom_read_from_stream<T>.
Incidentally, I second #NirFriedman's comment: is std::standard_layout really what you mean?

Class template operator overloading for fundamental and specific non-fundamental types

I am just writing on a MathVector class
template<typename T> MathVector
{
using value_type = T;
// further implementation
};
However, the class is thought to work with fundamental types but also with a, lets say, Complex class
template<typename T> Complex
{
using value_type = T;
// further implementation
};
which offers for example the member functions
template<typename T> Complex<T>& Complex<T>::operator*=(const Complex<T>& c);
template<typename T> Complex<T>& Complex<T>::operator*=(const T& c);
Now, for the MathVector class also a multiplication is definded:
template<typename T> MathVector<T>& MathVector<T>::operator*=(const MathVector<T>& c);
This is fine for T=double, but for T=Complex<double> I would like to have the possibility to multiply also with double without first converting it to Complex<double> (much more efficient).
This is aggravated by the fact that the Code should also work in CUDA device code (I ommited the specifier __host__ __device__ for brevity). This means that the standard library tools will not be helpful.
First I thought of an additional template parameter
template<typename T, typename U> MathVector<T>& MathVector<T>::operator*=(const U& c);
But this seems dangerous to me, because U can be a lot of more than T or T::value_type. (In fact I had this parameter also in the Complex class first - the compiler was not able any more to decide wich template to use, the one of the Complex class or the one of the MathVector class.)
The second idea is to use template specialization
template<typename T, typename U> MathVector<T>& MathVector<T>::operator*=(const U& c)
{
static_assert(sizeof(T) == 0, "Error...");
}
template<typename T> MathVector<T>& MathVector<T>::operator*=(const typename T::value_type& c)
{
// implementation
}
But this will not work with fundamental types any more!
I have seen the solutions of this (or a very similar) problem in C++ Operator Overloading for a Matrix Class with Both Real and Complex Matrices and Return double or complex from template function, but they are solved using the standard library in a way which is not possible for CUDA.
So my question is: Is there a way to overload the operator that works with fundamental types and with types that serve a value_type but not for others - without using std:: stuff that the nvcc compiler will reject?
You could make operator*= non-member function templates, and provide all the overloads, make SFINAE to take effect.
template<typename T>
MathVector<T>& operator*=(MathVector<T>& m, const MathVector<T>& c);
template<typename T>
MathVector<T>& operator*=(MathVector<T>& m, const T& c);
template<typename T>
MathVector<T>& operator*=(MathVector<T>& m, const typename T::value_type& c);
Then call them as:
MathVector<Complex<double>> m1;
m1 *= MathVector<Complex<double>>{}; // call the 1st one
m1 *= Complex<double>{}; // call the 2nd one
m1 *= 0.0; // call the 3rd one
MathVector<double> m2;
m2 *= MathVector<double>{}; // call the 1st one
m2 *= 0.0; // call the 2nd one
LIVE
With SFINAE, and decltype, you may do something like (c++11):
template<typename T, typename U>
auto
MathVector<T>::operator*=(const U& c)
-> decltype(void(std::declval<T&>() *= c), std::declval<MathVector<T>&>())
{
// Your implementation
}

why is std::plus a class template?

The C++ functor std::plus is implemented like
template<typename T>
struct plus
{
constexpr T operator+(const T& lhs, const T& rhs) const
{ return lhs+rhs; }
};
but there is also the specialisation
template<>
struct plus<void>
{
template< class T, class U>
constexpr auto operator()(T&& lhs, U&& rhs) const
-> decltype(std::forward<T>(lhs) + std::forward<U>(rhs))
{ return std::forward<T>(lhs) + std::forward<U>(rhs); }
};
which has the advantage that it can operate on any types, even differnt ones, as long as T+U is defined (for example std::string and const char*).
I was wondering why struct std::plus is not defined as a non-template with the functionality of the existing std::plus<void>? Are there any possible applications which could not be served by such an implementation?
Of course, this could have historical reasons (so that it cannot be changed). But if this is the only reason, wouldn't it be possible to change the template argument to default to void?
template<typename T=void> struct plus;
edit. I just realised that according to cppreference plus<T> defaults to plus<void> since C++14.
Arguably, plus<void>::operator+ could not have been implemented in C98++, but the following could
struct plus
{
template<class T>
T operator()(const T&lhs, const T&rhs) const
{ return lhs + rhs; }
};
which has exactly the same functionality as plus<T>, but avoids the template. So why was this not chosen instead? What was the logic/motivation behind the class template? (it violates the rule "do it simple if possible")
Templated member functions were not allowed in early versions of C++ (even those which allowed templated classes and free functions). That meant that functors like std::plus had to be fully specialized, rather than deferring specialization to the operator.
While I have already been penalized for second-guessing the reasons today, I will still dare to coin my view :)
I would assume, std::plus was made a template so that it could be speciailized and something else other than + be used for types which do not have + defined on them.

Policy to produce the result type of an arithmetic operation?

Consider the following example:
#include <iostream>
#include <string>
template <class Property>
struct MyClass
{
double _data;
};
template <class Property>
inline MyClass<Property> operator+(const MyClass<Property> lhs,
const MyClass<Property> rhs)
{
return {lhs._data + rhs._data};
}
int main()
{
MyClass<std::string> a{1.5};
MyClass<std::string> b{2.5};
MyClass<std::string> c = a + b;
std::cout<<c._data<<std::endl;
return 0;
}
It is simple and does not exhibit any problem of design (at least I think). Now consider that I want to be able to generate an operator+ (and all arithmetic operators) for classes with different property types. But there is no standard way to decide whether MyClass<Property1> + MyClass<Property2> should be a MyClass<Property1> or a MyClass<Property2>: so the good choice should be specified by the user. The question is the following: how to redesign the class (or anything else) to let the user provide a conversion strategy ? (I mean how would it be designed in the standard library or in boost ?)
EDIT: To clarify, the result type of MyClass<Property1> + MyClass<Property2> cannot be automatically generated by the compiler. This policy should be specified by the user. But how to design that in a clean way ?
Use type traits (as Kerrek SB pointed out in his comment).
template<typename T, typename U>
struct CommonPropertyTypeTraits : std::common_type<T,U>{}; //default version works if the types have a common type
std::common_type will only work if the type is implicitly convertable. If there is no common type which is implicitly convertable or if the user wants a different one they can specialize CommonPropertyTypeTraits:
template<>
struct CommonPropertyTypeTraits<MyType,MyOtherType> {
using type = SomeType;
}
The body of your function would then be:
template <class Property1, class Property2>
inline MyClass<typename CommonPropertyTypeTraits<Property1,Property2>::type> operator+(const MyClass<Property1> lhs, const MyClass<Property2> rhs)
{
return {lhs._data + rhs._data};
}
Note that this will give a pretty ugly error if there is not implicitly convertable common type and the user did not specialize the traits template. One could make a SFINAE test to make a better error:
template <class Property1, class Property2, typename=typename CommonPropertyTypeTraits<Property1,Property2>::type>
inline MyClass<typename CommonPropertyTypeTraits<Property1,Property2>::type> operator+(const MyClass<Property1> lhs, const MyClass<Property2> rhs)
{
return {lhs._data + rhs._data};
}
I'm still not quite certain what you ultimately want to use this for. If it is for dimensional analysis (i.e. tracking the types of different unit systems at compile time and making necessary conversions and issuing necessary errors) have a look at boost.Unit.

Functors: templated struct vs templated operator()

The usual pattern for standard library function objects is to have a templated struct with a non-template operator(). For example, std::less looks something like so:
template <typename T>
struct less
{
bool operator()(const T& lhs, const T& rhs) const {
return lhs < rhs;
}
};
std::vector<float> vec = ...;
std::sort(vec.begin(), vec.end(), less<float>{});
My question is, why is this better than a non-template struct with a templated operator()? It seems that the above functor would be equivalent in operation to:
struct less2
{
template <typename T>
bool operator()(const T& lhs, const T& rhs) const {
return lhs < rhs;
}
};
std::vector<float> vec = ...;
std::sort(vec.begin(), vec.end(), less2{});
except that we get the bonus of automatic type deduction. Even better, if we wanted to, we could compare different types, provided it made sense to do so:
struct less
{
template <typename T, typename U>
bool operator()(const T& lhs, const U& rhs) const {
return lhs < rhs; // compile error if operator<(T, U) is not defined
}
};
and from there it's obvious how we could use decltype to get a truly generic std::plus, for example. But the standard library doesn't do it that way.
Of course, I'm sure that I'm not the first person that this has occurred to, and I'm sure that there's a very good reason that the standards committee decided to go with the first pattern and not the second. I'm just not sure what it is. Can any of the gurus enlighten me?
When the original functors were created none of the needed language facilities (return type deduction, perfect forwarding) did exist to solve the problem. The current design also has the benefit of allowing users to specialize the functors for their own types, even if this should strictly not be necessary.
C++1y introduces void specializations for all the functors (and uses void as a default argument) to make them easier to use. They will deduce and perfectly forward arguments and return types. This will also allow heterogeneous comparisons.
You will be able to write code like:
std::sort(begin(v), end(v), less<>());
The paper introducing this change is N3421.