template parameter inference for std pair

Viewed 116

In the code below, X x1{{100,5.55}, "abc"} fails to compile ("candidate template ignored: couldn't infer template argument 'T1'"). I believe that this is because, template deduction does not consider implicit conversions, so the compiler does not treat the first argument as a std::pair.

Is there any way to make the X x1{{100,5.55}, "abc"} syntax work? I want to avoid explicit use of std::pair in the argument, because I am refactoring a large code base where this expression is used in many places.
(In the current code, the constructor is not templated, because it uses std::variant to create an omnibus class that handles multiple types with implicit constructors for int, double, std::string, etc. I am trying to replace this class with a templated constructor that preserves the underlying data types, to improve type safety.)

#include <utility>

struct X
{
template <class T1, class T2> X(std::pair<T1,T2>, const char*){}
};

int main()
{  
  X x{std::pair{100,5.55}, "abc"};  // OK
//  X x1{{100,5.55}, "abc"};        // Compile error
}
1 Answers

I don't have a perfect solution, but if I am understanding your constraints well enough, it is not a big deal if new code needs to explicitly use std::pair{}, but old code has to remain compatible.

What about introducing a simple compatibility struct and (ab)using aggregate initialization to provide a constructor overload?

#include <utility>
#include <iostream>

struct X
{
    template <class T1, class T2> X(std::pair<T1,T2>, const char*)
    {
        std::cout << __PRETTY_FUNCTION__ << '\n';
    }

    struct LegacyPair
    {
        int a;
        double b;
    };

    X(LegacyPair p, const char* text) :
        X(std::pair{p.a, p.b}, text)
    {}
};

int main()
{
    X x1{std::pair{100,5.55}, "abc"};
    X x2{{100,5.55}, "abc"};
    //X x3{{3.14,5}, "abc"}; // error
    X x4{std::pair{3.14,5}, "abc"};
}

This outputs:

  • X::X(std::pair<_T1, _T2>, const char*) [with T1 = int; T2 = double] for x1
  • X::X(std::pair<_T1, _T2>, const char*) [with T1 = int; T2 = double] for x2
  • An error for x3 (better than accidentally constructing a std::pair<int, double>). Because {} is aggregate initialization here, implicit conversion is not allowed, so this overload gets discarded. Which is good!
  • X::X(std::pair<_T1, _T2>, const char*) [with T1 = double; T2 = int] for x4
Related