How to implement an initializer list for user defined type? (analogus to std::vector initializer list)
Asked Answered
B

3

18

std::vector can be initialized as

std::vector<std::string> words1 {"the", "frogurt", "is", "also", "cursed"}; 

Reference

Now if want to achieve the similar functionality for one of my types, how do I go about doing it? How should I implement the constructor for this functionality?.

How does the standard support me achieving this (reference to standard will be most helpful)?. Basically, if you could teach me how it's implemented for std::vector it will be sufficient.

Can this also be done prior to C++11?

Also, can I have a POD struct type initializer list so that I can have values with different types to initialise my type?

Bascomb answered 9/5, 2013 at 5:31 Comment(0)
D
33

Create a constructor which takes a std::initializer_list as a parameter:

#include <vector>
#include <initializer_list>

template <typename T>
struct foo
{
private:
    std::vector<T> vec;

public:

    foo(std::initializer_list<T> init) 
      : vec(init)
    { }
};


int main()
{
    foo<int> f {1, 2, 3, 4, 5};
}

std::vector does this is almost exactly the same way (although utilizing begin() and end() - std::initializer_list has iterators much the same way as other containers do). From gcc:

  vector(initializer_list<value_type> __l,
     const allocator_type& __a = allocator_type())
  : _Base(__a)
  {
_M_range_initialize(__l.begin(), __l.end(),
            random_access_iterator_tag());
  }

Edit: I'm not 100% what you're trying to do, but you may simply be able to use uniform initialization to get what you want:

struct bar
{
private:

    int i;
    double j;
    std::string k;

public:

    bar(int i_, double j_, const std::string& k_)
      : i(i_), j(j_), k(k_)
    { }

};

int main()
{
    bar b {1, 2.0, "hi"};
}
Damara answered 9/5, 2013 at 5:37 Comment(14)
the values in the initializer list has to be of the same type right? can i have different type(as if like initializing a POD struct)?Bascomb
@Koushik They must be all of one type. It's effectively stored as an array of type T.Damara
oh so icant have a POD struct like initializer list is it?Bascomb
Note, the constructor is unnecessary for this uniform initialization example, because the type is an aggregate. (It works as long as there are no constructors.)Knoll
@Knoll With public member variables, yeah.Damara
@Damara thanks thats what i'm after:-) sorry it was confusingBascomb
What's the bonus comparing bar b(1, 2.0, "hi"); ?Tamica
@Tamica sugarcoating:-) the same way why we dont really need operator overloading.Bascomb
If you want your list-initialization to also work unsurprisingly with zero-size initializer-list, you also need to add a default constructor with the corresponding semantics. For more details see akrzemi1.wordpress.com/2013/05/14/empty-list-initializationRumple
@Rumple i recently started following your blog starting with "sept 2011's destructors that throw" . its really helpful. thanks for such a good site.Bascomb
@Rumple Thanks for that, I was also unaware of empty initializer lists causing problems.Damara
@Tamica Koushik is actually incorrect the bonus is uniform initialization prevents narrowing, look it up :)Charmion
@Koushik not sugar coating see above commentCharmion
@Charmion yeah i actually learnt about it after going to the link in above comment . Hope UniversE is not misguided by my comments.Bascomb
E
2

I guess if you want a custom initializer list, the following should suffice?

template <typename T>
class Initializer
{
private:
    T* _List;
    size_t len;

public:
    Initializer()
        : _List(nullptr), len(0) {}

    template <typename... _Rest>
    Initializer(T _first, _Rest... _rest)
        : _List(new T[sizeof...(_rest) + 1]{_first, _rest...}), 
          len(sizeof...(_rest) + 1) {}

    Initializer<T>& operator=(Initializer<T> &&) = delete; // move operator [Implement your own if you want.]
    Initializer<T>& operator=(const Initializer<T>& ) = delete; // copy operator [Implement your own if you want.]

    const T operator[] (size_t index) const noexcept
        { return _List[index]; }
    T& operator[] (size_t index) noexcept
        { return _List[index]; }

    const T* begin() const noexcept
        { return _List; }
    T* begin() noexcept
        { return _List; }

    const T* end() const noexcept
        { return &_List[len]; }
    T* end() noexcept
        { return &_List[len]; }

    size_t length() const noexcept
        { return len; }

    ~Initializer() noexcept
        { _List != nullptr ? delete[] _List : void(0); }
};

Examples: Passing it as a function constructor:

void func(Initializer<int> list)
{
    // Nothing. Absolutely nothing...
}

int main()
{
    func({ 104, 101, 108, 108, 111}); // Don't translate to ASCII ;)
}

Initializing it normally:

Initializer<const char*> AListOfStrings = {
   "This is a test",
   "Mic testing 123",
   "Oh good it works!"
};

Now I don't know if there are any edge cases to this way, but if there are you could perhaps let me know about it in the comments and i could try to fix it.

Anyway I think this should be ok.

Extra answered 7/9, 2022 at 18:37 Comment(0)
B
0

If someone is still seeking to get yet another answer for this question then you can get the benefit from typeid like:

template <typename Type>
void check_type( Type par ){
   if( typeid(Type) == typeid(std::string()) ) {} //check for string type
   if( typeid(Type) == typeid(int) ){} //is int

}
Beamon answered 19/6, 2020 at 8:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.