0

Situation like:

#include <utility>
#include <typeinfo>
#include <iostream>

struct C1
{
    char const* str; 
    template <typename T> operator T() const { std::cout << "Convert to " << typeid(T).name() << "\n"; return {}; }
};

struct C2
{
    C2(C1 const&) { std::cout << "C2(C1)\n"; }
    C2(std::initializer_list<std::pair<char const*, int>>) { std::cout << "C2(list)\n"; }
};

int main()
{
    C1 c1{};
    C2 c2{c1};
}

Output indicates that C2(list) is being called.

I would like C2(C1) to be called for C1 argument, but I need the parameters of std::initializer list to remain deducible and convertible, and I can not replace it with variadic-templated version. I just want to control the construction order, but here //2 is not even template. Suppose type std::pair can be deserialized in normal conditions. C++14 may be used

M.M
  • 138,810
  • 21
  • 208
  • 365
Роман Коптев
  • 1,555
  • 1
  • 13
  • 35
  • 1
    The issue is `{}` calls `initializer_list` constructor, which has precedence over the usual constructor. What is wrong with calling it `C2 c2(c1)`? Or do you want this specific syntax in your API? – romeric Oct 15 '15 at 03:16
  • 1
    I guess you should replace `operator T` with the actual types C1 can convert to. Or even better, use a named function instead of a conversion operator and actually call the function when required. – M.M Oct 15 '15 at 03:17
  • @romeric It is not interface only for me. If I'd use it internally I'd do so – Роман Коптев Oct 15 '15 at 03:19
  • @M.M It really deserializes objects with boost deserialization, It is for that porpose, to deserialize any kind of objects user wants, and I can't to replace it with concrete type – Роман Коптев Oct 15 '15 at 03:21
  • @M.M. I was trying. It is impossible to use a named function in my case. `C1` was designed as a proxy class and is returned by iterators of some container. The `C2` is noncopyable, nonmovable and nonallocatable whith `new` class. c2 = c1 must call a constructor of c2. Conversion c2 = c1.asT() can't be called, as moving and copying constructors for C2 is deleted. initializer_list really brakes all API – Роман Коптев Oct 15 '15 at 05:07
  • I have solved it by preventing conversion to type `std::pair`, and it is not very good. Another solution is to use boost preprocessor repetition macro definitions to generate several constructors with different count of `std::pair` parameters. But argument count in this case is limited. All other solutions I tried had serious issues for me. – Роман Коптев Oct 15 '15 at 08:00

2 Answers2

0

You may use the explicit keyword to prevent a constructor from implicit type conversions:

    explicit C2(std::initializer_list<std::pair<char const*, int>>) {} // 2

See: What does the explicit keyword mean in C++?

Community
  • 1
  • 1
AndreyS Scherbakov
  • 2,674
  • 2
  • 20
  • 27
  • yes. I can, but I need std::pair with convertible parameters to remain convertible, if I'll use explicit keywords, I'll lost conversion – Роман Коптев Oct 15 '15 at 03:08
  • You will need to add more constructor(s) performing allowed conversion (say, taking std::pair as a parameter). – AndreyS Scherbakov Oct 15 '15 at 03:11
  • It will be a dozen constuctors :) – Роман Коптев Oct 15 '15 at 03:13
  • I think this means that you don't really want the C1::operator T() . Maybe to consider explicit convertion methods like T& C1::asT(), const T& asT() const instead? – AndreyS Scherbakov Oct 15 '15 at 03:19
  • In this case it will trying to convert c1 even with explicit keyword. And with two versions of constructors with std::list, both explicit, will be a ambiguous overloading. It seems to be unnatural, but it is so. I have tried it. – Роман Коптев Oct 15 '15 at 03:36
  • The construction of std::initializer_list as a proxy object is not explicit – Роман Коптев Oct 15 '15 at 04:36
  • AndreyS Scherbakov. I was trying. It is impossible to use a named function in my case. `C1` was designed as a proxy class and is returned by iterators of some container. The `C2` is noncopyable, nonmovable and nonallocatable whith `new` class. c2 = c1 must call a constructor of c2. Conversion c2 = c1.asT() can't be called, as moving and copying constructors for C2 is deleted. initializer_list really brakes all API – Роман Коптев Oct 15 '15 at 05:08
  • I mean calling C2's constructor (i.e. initialization): C2 y(c1.asT()); – AndreyS Scherbakov Oct 15 '15 at 06:39
0

The following selects C2(C1) style for both style, {...} and ({...}) (in strict C++11 Standards, for the ({...}) style, it will select the second, because C1 is an aggregate and special rules applied for it. This is no longer the case for more recent Standards.). This works by making the second constructor no longer be an initializer constructor.

template<typename T>
struct id { typedef T type; };

struct C1 {
    char const* str; 
    template <typename T> operator T() const 
    { std::cout << "Convert to " << typeid(T).name() << "\n"; return {}; }
};

struct C2 {
    C2(C1);
    template<typename T = std::initializer_list<std::pair<char const*, int>>>
    C2(typename id<T>::type);
};

int main() {
    C1 c1{};
    C2 c2{c1};
    C2 c21({c1});
}
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • It is good. Only to use with initializer list I need put braces twice, like `C2 c22{{ {"bbb", 3}, {"aaa", 3} }};` and not just `C2 c22{ {"bbb", 3}, {"aaa", 3} };` – Роман Коптев Oct 17 '15 at 13:35