Search code examples
c++constructorc++17initializer-list

Use different constructor for class member based on class template parameter


Have a situation where a templated class constains a templated member and said member object must be constructed using the proper constructor (which depends on a class template). Simplified example:

#include <stdexcept>
#include <memory>
#include <iostream>
#include <vector>

template<int N=0>
struct A
{
    const int n_;

    A(int n, double)
        : n_{n}
    {
        static_assert(N <= 0);
    }

    A(double)
        : n_{N}
    {
        static_assert(N > 0);
    }
};


template<int N=0>
struct B
{
    const std::vector<int> m_;

    // Solution with unique_ptr class member to an A<N> object
    std::unique_ptr<A<N>> a_uptr_;
    B(const std::vector<int>& m, double x)
        :   m_{m},
            a_uptr_{N > 0 ? std::make_unique<A<N>>(x) : std::make_unique<A<N>>(m.size(), x)}
    {}


    // Solution with class member A<N> object
    // A<N> a_;
    // B(const std::vector<int>& m, double x)
    //  :   m_{m},
    //      a_{  <????????????????????> }
    // {}
};

int main()
{
    std::vector<int> m{1,2,3};
    B b1(m, 0);
    B<2> b2(m, 0); 
    std::cout << b1.a_uptr_->n_ << ", " << b2.a_uptr_->n_ << std::endl;
}

Q1: Is there a way to just have an A<N> class member (instead of a unique ptr) and have it call the correct constructor? Using an std::integer_sequence perhaps???

(Q2: Am I overthinking this, i.e. should I really be worried about performance of having to put the A<N> obj on the heap?)


Solution

  • Assuming the question is only about B, that A is supposed to not change... Then what you face is what I would call "complicated construction of a member in the initializer list" and the soltuion for this is generally a static method:

    #include <stdexcept>
    #include <iostream>
    #include <vector>
    
    template<int N=0>
    struct A {
        const int n_;   
        A(int n, double) : n_{n} {
            if constexpr (N > 0) throw  std::runtime_error("wrong constructor for N > 0");
        }
    
        A(double) : n_{N} {
            if constexpr (N <= 0) throw std::runtime_error("wrong constructor for N <= 0");
        }
    };
    
    
    template<int N=0>
    struct B {
        const std::vector<int> m_;    
        A<N> a_uptr_;
        B(const std::vector<int>& m, double x) 
          :   m_{m},
              a_uptr_{make_AN(m,x)}
        {}
    private:
        static A<N> make_AN(const std::vector<int>& m,double x) {
            if constexpr (N>0) return x;
            else return {m.size(),x};
        }
    };
    
    int main() {
        std::vector<int> m{1,2,3};
        B b1(m, 0);
        B<2> b2(m, 0); 
        std::cout << b1.a_uptr_.n_ << ", " << b2.a_uptr_.n_ << std::endl;
    }
    

    Or as mentioned in comments (Some programmer dude and n.m.), simply a_{N > 0 ? A<N>{m} : A<N>{m, x}}. Perhaps you are afraid of copies which are elided anyhow.

    However, you should consider to rewrite A such that it is a compile time error to call A<N>::A(int,double) with N > 0 and A<N>::A(double) with N <= 0.