2

I have a lot of code that currently works with dynamic size vectors (such as std::vector, Eigen::VectorXd, ...) and I would like it to work also with static size vectors (such as std::array, Eigen::Vector3d, ...). My code is templatized with respect to TVector in such a way that it assumes that TVector has just size and operator[]. But the big problem is construction.

There are no common grounds when it comes to construction of both static and dynamic vectors. I have decided to replace all calls to the constructor of TVector by the following hypothetical function:

template <typename TVector>
TVector createVector(std::size_t sizeAtInitialization)
{
    if (TVector has constructor taking an integral type)
        return TVector(sizeAtInitialization);
    else
        return TVector(); // sizeAtInitialization ignored
}

Such that a generic call to createVector<TVector> will become createVector<std::vector<double>>(3) or createVector<std::array<double, 3>>(3).

std::is_default_constructible will not help me here, since both vector types are default constructible.

However, I am not sure that such a thing is even possible.

I have studied the static if here and member function detection here. It all seems extremely complicated and since the static_if uses lambdas which must return to an intermediate result, which must already be constructed, I am not sure it leads anywhere.

Community
  • 1
  • 1
Martin Drozdik
  • 12,742
  • 22
  • 81
  • 146
  • 4
    Well you could make a type trait and define it for each type used. That trait could then be evaluated with SFINAE to pick the static or dynamic initialization function. – NathanOliver Jan 26 '17 at 17:17
  • @NathanOliver are there any benefits of that versus simply specializing the `createVector` for each type? Just curious. – Rudolfs Bundulis Jan 26 '17 at 17:46
  • @RudolfsBundulis I think it is easier to spread that way. Now all someone has to do to use your function is add the appropriate type trait instead of having to add their own specialization of the function. – NathanOliver Jan 26 '17 at 17:51

2 Answers2

2

You can use std::is_constructible together with a bit of sfinae and std::enable_if

using namespace std;

template <class T>
using IntegerConstructible =
    typename enable_if<is_constructible<T, int>::value, T>::type;

template <class T>
using NotIntegerConstructible =
    typename enable_if<!is_constructible<T, int>::value, T>::type;

template<class T>
auto createVector(int size)
  -> IntegerConstructible<T> {
  return {size};
}

template<class T>
auto createVector(int size)
  -> NotIntegerConstructible<T>{
  return {};
}

int main(){
  auto x = createVector<std::vector<int>>(3);
  auto y = createVector<std::array<int,3>>(3);
  return 0;
}

IntegerConstructible and NotIntegerConstructible are alias templates which are defined as long as the template argument can (or can't) be constructed. When defined IntegerConstructible<T> = T.

So only one of the two createVector functions has a valid return type, the other one cannot be called. This allows the compiler to choose the right overload.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
Tim
  • 693
  • 3
  • 11
  • But the `Eigen::Matrix` template class which is a base of both `Eigen::VectorXd` and `Eigen::Vector3d` has a constructor with an int parameter. – Rudolfs Bundulis Jan 26 '17 at 18:17
1

I love SFINAE (+1 for Tim) but I think that tag-dispatcing is clearer for this type of problem

#include <array>
#include <vector>
#include <iostream>
#include <type_traits>

template <typename T>
T cVH (int size, std::true_type const &)
 { std::cout << "with size" << std::endl; return { size }; }

template <typename T>
T cVH (int, std::false_type const &)
 { std::cout << "without size" << std::endl; return { }; }

template <typename T>
T createVector (int size)
 { return cVH<T>(size, typename std::is_constructible<T, int>::type{}); }

int main()
 {
  auto x = createVector<std::vector<int>>(3);   // print "with size"
  auto y = createVector<std::array<int,3>>(3);  // print "without size"
 }

--- EDIT ---

If there isn't a type-traits that can select the right value for all your tyes, you can create one yourself as (struct withSize) follows

#include <array>
#include <vector>
#include <iostream>
#include <type_traits>

template <typename>
struct withSize;

template <typename T>
struct withSize<std::vector<T>>
 { using type = std::true_type; };

template <typename T, std::size_t N>
struct withSize<std::array<T, N>>
 { using type = std::false_type; };

// others specializations of withSize for Eigen::Vector3d, Eigen::Matrix, etc.

template <typename T>
T cVH (int size, std::true_type const &)
 { std::cout << "with size" << std::endl; return { size }; }

template <typename T>
T cVH (int, std::false_type const &)
 { std::cout << "without size" << std::endl; return { }; }

template <typename T>
T createVector (int size)
 { return cVH<T>(size, typename withSize<T>::type{}); }

int main()
 {
  auto x = createVector<std::vector<int>>(3);   // print "with size"
  auto y = createVector<std::array<int,3>>(3);  // print "without size"
 }
max66
  • 65,235
  • 10
  • 71
  • 111