10

In Is it safe to define a specialization for std::array the questioner asks if it's safe to specialize std::array for a program defined type which has an expensive default constructor. The goal is to instead initialize all elements in the std::array by using a cheap constructor instead of the default constructor.

I'm not questioning that design at this point. I'm curious about the possibilities and, to that problem, I suggested to make use of brace elision on initialization of aggregates, define an inner class and use a special constructor via a tag:

namespace foo {
struct cheap_tag_t {};
inline constexpr cheap_tag_t cheap_tag;

class bar {
public:
    inline bar() { std::cout << "expensive ctor\n"; }
    inline bar(int) { std::cout << "int ctor\n"; }
    inline bar(cheap_tag_t) { std::cout << "cheap ctor\n"; }
};
}  // namespace foo
template <std::size_t N>
    requires (N != 0)     // let the standard array take care of N==0
struct std::array<foo::bar, N> {
    struct inner_array {
        foo::bar data[N];
    };

    inner_array m_data{
        []<std::size_t... I>(std::index_sequence<I...>) -> inner_array {
            return {(void(I), foo::cheap_tag)...};
        }(std::make_index_sequence<N>())
    };

    // ... fully compliant member functions ...
};

I can't find anything in the standard prohibiting this odd specialization. I say it's odd because it has an, to an end user, unexpected feature shown last below:

std::array<foo::bar, 0> b0;    // not using the specialization at all
std::array<foo::bar, 2> b1;    // cheap ctor
std::array<foo::bar, 2> b2{};  // cheap ctor
std::array<foo::bar, 2> b3{1}; // one "int ctor" + one "expensive ctor" (surprised user)

To the language lawyering question:

  • Given that the rest of the specialization will meet the standard library requirements for the original std::array template, does the above part of the specialization agree with the standard1, 2?

1. "the standard" I've (tried to) read is the C++23 draft. namespace.std/2

Unless explicitly prohibited, a program may add a template specialization for any standard library class template to namespace std provided that
(a) the added declaration depends on at least one program-defined type and
(b) the specialization meets the standard library requirements for the original template.

If there's a different answer to my question in an earlier, or future, standard that you know of, feel free to mention it. It's not required to get an accepted answer.

2. If the above part of the specialization is ok according to "the standard", but you see other problems ahead making it hard/impossible to fulfill the requirements, please feel free to mention those as well. Again, not required.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • "*by using a cheap constructor*" Define "cheap". It's *never* going to be faster than aggregate initialization, since that has the power to simply use the initializing expressions to directly initialize the members. Constructors require the creation of arguments, which can be copied/moved into the members. – Nicol Bolas Dec 21 '22 at 18:49
  • @NicolBolas The default and "cheap" constructors are those in the class for which the `std::array` is specialized. The `std::array` will be aggregate initialized for sure, by a `cheap_tag_t`, or via an initializer list or by `default` (which the OP tries to avoid). – Ted Lyngmo Dec 21 '22 at 19:04
  • My suggestion would be to make the expensive constructor take a `expensive_tag_t` parameter instead – Mooing Duck May 24 '23 at 16:48
  • Not sure, might be that the example takes too much space and distracts from the actual question whether it is allowed to specialize at all. The important bits are in a footnote – 463035818_is_not_an_ai May 24 '23 at 16:49
  • @MooingDuck Yes, that would probably be the wise choice. I stuck with what was presented in [the original question](https://stackoverflow.com/questions/74868066/is-defining-a-specialization-for-stdarray-allowed-will-it-result-in-undefined) and got interested in if this would meet the requirements. – Ted Lyngmo May 24 '23 at 16:50
  • @463035818_is_not_a_number At the time I wrote the question I was very interested in if the parts I presented (my solution to the original problem) would in it self _"meet the standard library requirements"_, so I put much focus on those parts. – Ted Lyngmo May 24 '23 at 16:52

1 Answers1

4

Your specialization cannot be used in the following way:

struct anyconv {
    template<typename T>
    operator T() { return T(); }
};

std::array<foo::bar, 2> x = {anyconv{}, anyconv{}};

However, anyconv is implicitly convertible to foo::bar and so by the requirement in [array.overview]/2 it should be possible to list-initialize std::array<foo::bar, 2> with 2 initializer-clauses that are of type anyconv.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • I'm not 100% - what in C++ would my idea be violating? I worked it out of an idea that "anything goes" - as long as you fultill these pretty hard things to fulfill ... – Ted Lyngmo May 26 '23 at 23:37
  • @TedLyngmo As you quote in the question "_provided that [...] the specialization meets the standard library requirements for the original template_". One of these requirements is [array.overview]/2 which requires that my usage example ought to be well-formed, but it isn't with your partial specialization because the first `anyconv{}` can and will initialize the whole `inner_array`, leaving nothing for the second `anyconv{}` to initialize. – user17732522 May 26 '23 at 23:42
  • Yes, I now see what you mean. `anyconv` converts to `inner_array` instead of `T`. Bummer, but I'll have to accept it. Thanks! – Ted Lyngmo May 28 '23 at 06:31