最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c++ - Provide conversion between types while keeping designated initializers available - Stack Overflow

programmeradmin1浏览0评论

Imagine i have some structure like this:

template <typename T>
struct ag_init {
    T a, b;
};

The creation of this is nice to write and pretty straight forward thanks to designated initializers:

ag_init a1{.a = 1, .b = 2};

Imagine now that i have some custom types that can implicitly convert between each other:

struct float_like {
    float i;
};

struct double_like {
    double d;
    double_like() = default;
    double_like(double) {}
    double_like(float_like) {}
};

This is common, for eg. glm::fvec3 can implicitly convert to glm::dvec3. I want my struct to inherit this nice conversion ability. AKA i want to be able to do this:

ag_init a1{.a = float_like{1}, .b = float_like{2}};
ag_init a2{.a = double_like{1}};

a2 = a1;                      // assignment
ag_init<double_like> a4{a1};  // converting construction

Assignment is easy:

struct ag_init {
   ...
   template <std::convertible_to<T> U>
      requires (!std::is_same_v<T, U>) // Prevents trashing of other automatically generated operators.
   ag_init& operator=(ag_init<U> other) {
        return *this;
    }

Now a2 = a1 works as expected. But if i do:

template <std::convertible_to<T> U>
  requires (!std::is_same_v<T, U>)
    ag_init(ag_init<U> other) {}

I have provided a constructor. This: a. Makes it not default constructable. b. Makes it not aggregate. Which means my nice designated initializers cannot work.

Is there a smart work around to make this possible while keeping aggregate initialisation?

The best i have come up with is something like:

// A helper for allowing designated initializers
template <typename T>
struct ag_ag
{
    T a, b;
};

template <typename T>
struct ag_init {

    T a, b;

    ag_init() = default;

    template <std::convertible_to<T> U>
        requires (!std::is_same_v<T, U>)
    ag_init& operator=(ag_init<U> other) {
        return *this;
    }

    template <std::convertible_to<T> U>
        requires (!std::is_same_v<T, U>)
    ag_init(ag_init<U> other) {}

    // A little syntactic suger, makes a copy though :( 
    ag_init(ag_ag<T> i) : a{i.a}, b{i.b} {}

    template <typename U>
    ag_init& operator=(ag_ag<U> i) {
        return *this;
    }

};

This allows something close:

ag_init a1 = ag_ag{.a = float_like{1}, .b = float_like{2}};

But there is a lot not to like:

  1. Copies
  2. Having to write the name of the special underlying initializer thing.
  3. Overall this is less nice than just a constructor that takes a and b. (Might be different in a case where there are a lot of members to initialize).

As suggested by @n. m. could be an AI.

Can we add a conversion operator. I assume we mean this:

    template <typename U>
        requires (std::convertible_to<T, U> && !std::is_same_v<T, U>)
    operator ag_init<U>() const {
        return {};
    } 

The answer is, this partially solves our conversion issue. This:

ag_init<double_like> a4(ag_init<float_like>);

Compiles, but this:

ag_init<double_like> a4{ag_init<float_like>}; // Curly brace init

Still fails to compile with:

error: no viable conversion from 'ag_init<float_like>' to 'double_like'

note: candidate template ignored: could not match 'ag_init' against 'double_like'

43 | operator ag_init() const {

Perhaps there is a better way to write it?

Live example

发布评论

评论列表(0)

  1. 暂无评论