0

Lets say I have two classes

template <int A_, int B_>
class One {
    public:
        static constexpr auto A = A_;
        static constexpr auto B = B_;
        const int C;
        One(int C) : C(C) {}
};

class Two {
    public:
        const int A;
        const int B;
        const int C;
        Two(int A, int B, int C) : A(A), B(B), C(C) {}
};

The only difference being that class One takes the A and B parameters at compile time, while Two takes the parameters at runtime. There exist multiple operations on these essentially equivalent types, so they can be written generically:

template <typename T>
auto min(const T& a, const T& b) {
    if (a.C < b.C) {
        return T(a.A+b.A, a.B+b.B, a.C);
    } else {
        return T(a.A+b.A, a.B+b.B, b.C);
    }
}

The issue with the above is the construction of the output object. The two types One and Two can be used in the same way to access the A, B, and C members, getting the types of them, etc. However, because there is a disparity in the construction of the objects (feeding function arguments vs feeding template arguments), if the operation requires the creation of a new object, it cannot be written generically.

I have tried non-type template argument deduction, but that is not currently possible. Is there any way around this? Or am I doomed to copying code?

ktb
  • 1,498
  • 10
  • 27
  • How about only using `Two` but providing a `constexpr` constructor? Then you could construct and use a `Two` at compile-time. – François Andrieux Jan 04 '19 at 15:19
  • @FrançoisAndrieux The `C` parameters is _always_ given at runtime, but `A` and `B` _may_ be known at compile-time. The idea behind `One` is to allow the compiler to use information to optimize the operations on the `C` values. Performance is a serious issue here. – ktb Jan 04 '19 at 15:25
  • Have you conclusively determined that `Two` is *not* already optimized that way? – François Andrieux Jan 04 '19 at 15:27
  • @FrançoisAndrieux That would rely on constant propogation, which is not guaranteed; and when the types are stored, they take up additional memory. The simulations that are to be run with this library already take way too long, every cycle and byte off can make a difference. It seem that C++ doesn't like compile-time and runtime calculations co-mingling, there might be another way to write the library to keep it happy. – ktb Jan 04 '19 at 15:35
  • Is there any chance C++20 might see non-type template parameters replaced with a constexpr/consteval system? I imagine it could be used much like the const modifer. – ktb Jan 04 '19 at 16:00
  • The most straight forward solution I can see is to use `One` and `Two` in templated context where you could provide either one. If they need to be used interchangeably at run time, it may be very difficult to do in a way that is worth the tiny performance improvement you are reaching for (in the sense that any abstraction may out weight the gains). – François Andrieux Jan 04 '19 at 16:36

1 Answers1

1

You might create your constant int class similar to std::integral_constant but extended with needed function, something like:

template <int N> struct int_c
{
    static constexpr int value = N;
};

template <int N1, int N2>
constexpr int_c<N1 + N2> operator + (int_c<N1>, int_c<N2>) { return {}; }

template <int N>
std::ostream& operator << (std::ostream& os, int_c<N>) { return os << N; }

Then change your class to have near interface, especially for constructor add factory method:

template <int A_, int B_>
class One {
public:
    static constexpr int_c<A_> A{};
    static constexpr int_c<B_> B{};
    const int C;
    One(int C) : C(C) {}

    template <typename IA, typename IB>
    static One<IA::value, IB::value> Create(IA, IB, int C) { return {C}; } 
};

class Two {
public:
    const int A;
    const int B;
    const int C;
    Two(int A, int B, int C) : A(A), B(B), C(C) {}

    static Two Create(int A, int B, int C) { return {A, B, C}; } 
};

Then your common code may look like:

template <typename T1, typename T2>
auto min(const T1& a, const T2& b) {
    if (a.C < b.C) {
        return T1::Create(a.A + b.A, a.B + b.B, a.C);
    } else {
        return T1::Create(a.A + b.A, a.B + b.B, b.C);
    }
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302