3

I have a template class with a member whose type depends on the class' template argument. The class has a template constructor. How do I specialize the constructor for different cases of the class' template argument that determines the type of the said member. The different types in question are classes with different constructor signatures, and I'd like to call the member's constructor in the initializer list. Is there a workaround? I would like to avoid resorting to factory functions, because I don't want to rely on the move constructor of the member being cheap.

Edit: Here's a code example,

template <typename T, typename C>
struct foo {
  T m;
  C container;
  using value_type = typename C::value_type;

  template <typename... Args>
  foo(Args&&... args)
  : m(std::forward<Args>(args)...), container(/*different stuff here*/)
  {}
};

My goal is to properly initialize the container regardless of whether C is a std::vector or std::array. If std::is_arithmetic<value_type>::value==true, I want to initialize the container with all zeros (this is where the issue with different constructor signatures comes in). If std::is_arithmetic<value_type>::value==false, I want to default initialize.

SU3
  • 5,064
  • 3
  • 35
  • 66
  • 1
    A code sample would go much further in describing your problem. Are you looking for something like this - http://coliru.stacked-crooked.com/a/5d9ea40421963b70 ? – Praetorian Jan 04 '17 at 21:49
  • I'll edit my answer with the example you've supplied as soon as I get the opportunity. – François Andrieux Jan 05 '17 at 02:04

1 Answers1

4

Your question is a little bit tricky to understand without an example. My understanding of it is that you want to specialize the constructor of a class template and construct it's member differently for different template arguments. If this isn't correct, let me know and I will adjust my answer.

Again without an example it's hard to know what you do and do not understand. But generally this is done the same way as you would specialize other methods. Declare all the specializations in your headers and implement them in your implementation file (unless they are partial specializations!). Remember to use template<> when specializing. Here is an example :

struct t_param_for_int {};
struct t_param_for_double {};


// t_member's constructor is very different depending on T
template<class T> struct t_member {};

template<> struct t_member<int> {
    explicit t_member(const t_param_for_int value) {};
};

template<> struct t_member<double> {
    explicit t_member(const t_param_for_double value) {};
};

// Foo has a t_member and a constructor
template<class T>
struct foo
{
    foo();

    t_member<T> member;
};

// Declare specialization
template<> foo<int>::foo();
template<> foo<double>::foo();

// Specialization implementations (in a .cpp)
template<> foo<int>::foo() : member(t_param_for_int{})
{ }

// Specialization implementations (in a .cpp)
template<> foo<double>::foo() : member(t_param_for_double{})
{ }

int main()
{
    foo<int> int_foo;
    foo<double> dbl_foo;
    return 0;
}

Edit: In response to the edit to the question.

You cannot specialize a constructor in this case. The best solution is likely going to be to use a helper structs to do the actual initialization. You mentioned you want to initialize your containers with some number of 0 or default constructed elements T. However you didn't specify how large your containers should be. I've constructed an example to illustrate the solution of using helper structs using bogus container sizes.

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

template<typename T, typename C>
struct helper_init;

template<typename T>
struct helper_init<T, std::vector<T>> {
    static std::vector<T> init() {
        return std::vector<T>(3, T{});  // init your vector with 3 elements
    }
};

template<typename T>
struct helper_init<T, std::array<T, 2>> {
    static std::array<T, 2> init() {
        return {};  // init your array with 2 elements
    }
};

template <typename T, typename C>
struct foo {
  T m;
  C container;
  using value_type = typename C::value_type;

  template <typename... Args>
  foo(Args&&... args)
  : m(std::forward<Args>(args)...)
  , container(helper_init<T, C>::init())
  {}
};

int main()
{
    foo<int, std::vector<int>> vec_foo(5);
    foo<std::string, std::array<std::string, 2>> str_foo("hello");

    // Output to illustrate

    // The size of the containers are 3 and 2 (see container initialization)
    std::cout << vec_foo.container.size() << ' ' 
        << str_foo.container.size() << std::endl;

    // The m members contain the assigned values
    std::cout << vec_foo.m << " \'" << str_foo.m << '\'' << std::endl;

    // The containers are zero or default initialized
    std::cout << vec_foo.container.front() << " \'" << 
        str_foo.container.front() << '\'' << std::endl;
    return 0;
}

For the second part of your question, initializing to 0 to arithmetic types and default constructing for class types, the language already has a feature for this.

std::array specifically says this about it's construction.

initialized the array following the rules of aggregate initialization

Then aggregate initialization says this.

If the number of initializer clauses is less than the number of members or initializer list is completely empty, the remaining members are value-initialized.

Finally value initialization says this.

1) if T is a class type with at least one user-provided constructor of any kind, the default constructor is called;

4) otherwise, the object is zero-initialized.

This allow us to do this std::array<T, 10> my_array{}; and have ten zeroed or default constructed Ts. We can also do std::vector<T> my_vector(10, T{}); to get the same results (T{} is value constructed`).

Edit 2: Here is another solution that more adheres to the question's requirements using a [delegating constructor] and tag dispatching.

#include <array>
#include <string>
#include <vector>


// Tags for supported containers
struct t_tag_array {};
struct t_tag_vector {};

// Tag dispatching
template<class T, size_t S>
struct t_container_tag {};

template<class T, size_t S>
struct t_container_tag<std::vector<T>, S> {
    using type = t_tag_vector;
};

template<class T, size_t S>
struct t_container_tag<std::array<T, S>, S> {
    using type = t_tag_array;
};

// Helper to fetch the size of an std::array
template<typename>
struct array_size;

template<typename T, size_t S>
struct array_size<std::array<T, S>> {
    static const auto value = S;
};

template <typename C, size_t S = array_size<C>::value>
struct foo 
{
    using value_type = typename C::value_type;

    // Constructor
    template<typename... Args>
    foo(Args&&... args) : foo(typename t_container_tag<C, S>::type{}, std::forward<Args>(args)...) {}

    // Specialized constructor for vectors
    template<typename... Args>
    foo(t_tag_vector &&, Args&&... args) : m(std::forward<Args>(args)...), container(S, value_type{}) {}

    // Specialized constructor for arrays
    template<typename... Args>
    foo(t_tag_array &&, Args&&... args) : m(std::forward<Args>(args)...), container{} {}

    value_type m;
    C container;
};
François Andrieux
  • 28,148
  • 6
  • 56
  • 87
  • 2
    Hint: delegating constructors and tag dispatching. Or SFINAE, delayed to the constructor instantiation (using an additional dummy template parameter). – bogdan Jan 05 '17 at 10:31
  • 2
    @bogdan I've done some research based on your hints and I've come up with an example (very good hints!). I'm not confident enough in my solution to post it as answer but I would still like to present it in the context of this question for some feedback. Could you take a quick look at what I have and give me some feed back? [pastebin](http://pastebin.com/34brLjeL) – François Andrieux Jan 05 '17 at 15:53
  • @bogdan I had tried sfinae before I posted the question (http://pastebin.com/NrpHFeM7), but I get `template cannot be overloaded`. – SU3 Jan 05 '17 at 18:12
  • @FrançoisAndrieux I think you should add your example with delegate constructors to your answer, so that others can see it too, if the pastebin expires. – SU3 Jan 05 '17 at 18:19
  • @SU3 I will definitively do that as soon as I get the opportunity. – François Andrieux Jan 05 '17 at 18:21
  • 1
    @SU3 Yes, because you're actually trying to redefine the same template. Default template arguments are not part of the template signature. You need to change something in the template parameters or function parameters (or function return type even) in order to actually declare different overloaded templates. For example: http://melpon.org/wandbox/permlink/kaCBcIbzAJU67X4t – bogdan Jan 05 '17 at 19:43
  • @bogdan Thanks for the explanation! Could you please also explain why this (http://pastebin.com/wBd1pz52) works then? – SU3 Jan 05 '17 at 21:55
  • @SU3 For the first initialization, both `foo(const std::vector&)` and `foo, void>(std::initializer_list)` are viable; the second one is chosen because a list-initialization sequence that converts to `std::initializer_list` is better than one that doesn't. For the second initialization, a specialization of the constructor template is not added to the overload set because it's disabled by SFINAE, so we're left with only one constructor (not a great choice because it creates a temporary `std::array` (to bind the reference) and then copies it into `container`). – bogdan Jan 06 '17 at 01:35