Reputation: 495
I can imagine the following code:
template <typename T> class X
{
public:
T container;
void foo()
{
if(is_vector(T))
container.push_back(Z);
else
container.insert(Z);
}
}
// somewhere else...
X<std::vector<sth>> abc;
abc.foo();
How to write it, to successfully compile? I know type traits, but when I'm defining:
template<typename T> struct is_vector : public std::false_type {};
template<typename T, typename A>
struct is_vector<std::vector<T, A>> : public std::true_type {};
It doesn't compile:
error: no matching function for call to 'std::vector<sth>::insert(Z)'
static_assert also isn't that what I'm looking for. Any advices?
Here's a short example of what I want to achieve (SSCCE): http://ideone.com/D3vBph
Upvotes: 19
Views: 7456
Reputation: 21
in C++20 using requires expression:
#include <type_traits>
#include <concepts>
#include <vector>
template<class T>
static constexpr bool is_vector_v = requires {
requires std::same_as<std::decay_t<T>,
std::vector<typename std::decay_t<T>::value_type> >;
};
and in code:
template<class T>
void foo() {
if constexpr (is_vector_v<T>)
container.push_back(Z);
else
container.insert(Z);
}
Upvotes: 0
Reputation: 1835
If you use constexpr if
, you were doing it right. This C++17 code compiles:
#include <iostream>
#include <type_traits>
#include <vector>
#include <list>
template<typename T> struct is_vector : public std::false_type {};
template<typename T, typename A>
struct is_vector<std::vector<T, A>> : public std::true_type {};
template <typename T>
class X
{
public:
T container;
void foo()
{
if constexpr(is_vector<T>::value){
std::cout << "I am manipulating a vector" << std::endl;
// Can access container.push_back here without compilation error
}
else {
std::cout << "I am manipulating something else" << std::endl;
}
}
};
int main() {
X<std::vector<int>> abc;
abc.foo(); // outputs "I am manipulating a vector"
X<std::list<int>> def;
def.foo(); // outputs "I am manipulating something else"
}
Upvotes: 1
Reputation: 4849
Here's the typical method using void_t:
template <typename T>
using void_t = void; // C++17 std::void_t
template <typename C, typename = void> // I'm using C for "container" instead of T, but whatever.
struct has_push_back_impl : std::false_type {};
template <typename C>
struct has_push_back_impl<C, void_t<decltype(std::declval<C>().push_back(typename C::value_type{}))>>
: std::true_type {}; // Note that void_t is technically not needed in this case, since the 'push_back' member function actually returns void anyway, but it the general method to pass the type into void_t's template argument to obtain void. For example, the 'insert' function from std::set and std::map do NOT return void, so 'has_insert' will need to use void_t.
template <typename C>
using has_push_back = has_push_back_impl<C>; // void passed to the second template argument by default, thus allowing the second specialization to be used instead of the primary template whenever C has a push_back member function.
This method will work for has_insert
for associative containers, even though std::set
, std::map
's insert
function return std::pair<typename T::iterator, bool>
while std::multimap::insert
returns std::multimap::iterator
(this is one case where Ze Blob's method will not work).
Upvotes: 1
Reputation: 2957
An alternative worth considering is to detect the presence of the push_back function using SFINAE. This is slightly more generic since it'll translate to other containers that implement push_back.
template<typename T>
struct has_push_back
{
template<typename U>
static std::true_type test(
decltype((void(U::*)(const typename U::value_type&)) &U::push_back)*);
template<typename>
static std::false_type test(...);
typedef decltype(test<T>(0)) type;
static constexpr bool value =
std::is_same<type, std::true_type>::value;
};
Note that it currently only detects push_back(const T&)
and not push_back(T&&)
. Detecting both is a little more complicated.
Here's how you make use of it to actually do the insert.
template<typename C, typename T>
void push_back_impl(C& cont, const T& value, std::true_type) {
cont.push_back(value);
}
template<typename C, typename T>
void push_back_impl(C& cont, const T& value, std::false_type) {
cont.insert(value);
}
template<typename C, typename T>
void push_back(C& cont, const T& value) {
push_back_impl(cont, value, has_push_back<C>::type());
}
std::vector<int> v;
push_back(v, 1);
std::set<int> s;
push_back(s, 1);
Honestly, this solution became a lot more complicated then I originally anticipated so I wouldn't use this unless you really need it. While it's not too hard to support const T&
and T&&
, it's even more arcane code that you have to maintain which is probably not worth it in most cases.
Upvotes: 6
Reputation:
Using insert only:
#include <iostream>
#include <vector>
#include <set>
template <typename T>
class X
{
public:
T container;
template <typename U>
void insert(const U& u) {
container.insert(container.end(), u);
}
};
int main() {
X<std::vector<int>> v;
v.insert(2);
v.insert(1);
v.insert(0);
for(std::vector<int>::const_iterator pos = v.container.begin();
pos != v.container.end();
++pos)
{
std::cout << *pos;
}
std::cout << '\n';
X<std::set<int>> s;
s.insert(2);
s.insert(1);
s.insert(0);
for(std::set<int>::const_iterator pos = s.container.begin();
pos != s.container.end();
++pos)
{
std::cout << *pos;
}
std::cout << '\n';
}
Upvotes: 5
Reputation: 8824
It is named tag dispatching :
#include <vector>
#include <set>
#include <type_traits>
template<typename T> struct is_vector : public std::false_type {};
template<typename T, typename A>
struct is_vector<std::vector<T, A>> : public std::true_type {};
template <typename T>
class X {
T container;
void foo( std::true_type ) {
container.push_back(0);
}
void foo( std::false_type ) {
container.insert(0);
}
public:
void foo() {
foo( is_vector<T>{} );
}
};
// somewhere else...
int main() {
X<std::vector<int>> abc;
abc.foo();
X<std::set<int>> def;
def.foo();
}
Upvotes: 34