0

Consider the following reference wrapper:

template <class T>
struct wrapper
{
    wrapper(T& x): reference{x} {}
    void set(const T& x) {reference = x;}
    T& get() const {return reference;}
    T& reference;
};

I am wondering:

  • How to declare a const reference wrapper through a template alias only template <class T> using const_wrapper = /* const wrapper<T> or wrapper<const T>?*/
  • How to change the wrapper struct to make the preceding point possible if it is not possible in this state?
  • How to solve the following problem: int i = 42; wrapper<const char> w(i); will compile but not work (I would like to block the constructor)
  • For what exact problem, iterator and const_iterator general have two different implementations?
Vincent
  • 57,703
  • 61
  • 205
  • 388

3 Answers3

2

How to declare a const reference wrapper through a template alias only template <class T> using const_wrapper = /* const wrapper<T> or wrapper<const T>?*/

Obviously that would be:

template <class T> using const_wrapper = wrapper<const T>;

The contained type is const, not the wrapper.

Be advised however that your set function cannot be called if T is const. This is for obvious reasons; you can't change a const value.

How to solve the following problem: int i = 42; wrapper<const char> w(i); will compile but not work (I would like to block the constructor)

This is actually a bit complicated. What you have to do is cause compilation to fail if the user tries to use a type that does not exactly match T. To do that, you need to use the =delete feature of C++11:

template <class T>
struct wrapper
{
    wrapper(T& x): reference{x} {}
    template<typename U>
        wrapper(const U &) = delete;
    //...
};

That second constructor will be used when anyone passes a type that is not an exact match for T. And since it is deleted, you get a compiler error when people attempt to use it.

For what exact problem, iterator and const_iterator general have two different implementations?

Who says that they do? They are not even required to be different types (consider the iterators for set for example), let alone required to have different implementations. They are simply distinct type aliases.

Mr Lister
  • 45,515
  • 15
  • 108
  • 150
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • I don't think that `wrapper;` is correct. You can't update a reference itself (see the comments about `set(const T&)`). If this is what OP wants to do he needs to change the code to using a pointer as member - as `std::reference_wrapper` does. – Simon Kraemer Dec 04 '15 at 16:47
  • @SimonKraemer: He wanted to know how to apply `const` to the type contained by his container; what I posted will do that. I never claimed that all of the functions defined in his container would still *work*. Also, it's not clear what he actually wants his `set` function to do. – Nicol Bolas Dec 04 '15 at 16:55
  • 2
    `vector` isn't allowed, I think. `set` is a better example. – T.C. Dec 04 '15 at 18:38
0
  • How to declare a const reference wrapper through a template alias only template <class T> using const_wrapper = /* const wrapper<T> or wrapper<const T>?*/

It depends what you want it to mean, which you haven't made clear.

  • How to change the wrapper struct to make the preceding point possible if it is not possible in this state?

See above.

  • How to solve the following problem: int i = 42; wrapper<const char> w(i); will compile but not work (I would like to block the constructor)

Add a partial specialization

template <class T>
struct wrapper<const T>
{
    wrapper(const T& x): reference{x} {}
    wrapper(T&) = delete;
    void set(const T& x) {reference = x;}
    const T& get() const {return reference;}
    const T& reference;
};

Or add a suitably constrained constructor to the primary template:

template <class T>
struct wrapper
{
    wrapper(T& x): reference{x} {}
    template<typename U, typename = std::enable_if_t<std::is_const<T>{} && std::is_same<T, const U>{}, void>>
    wrapper(U&) = delete;
    void set(const T& x) {reference = x;}
    T& get() const {return reference;}
    T& reference;
};
  • For what exact problem, iterator and const_iterator general have two different implementations?

Generally it should be possible to implicitly convert iterator to const_iterator but not the other way around. So you either need a SFINAEd constructor that only exists for the const_iterator form, or you have two implementations, one that supports the extra conversion and one that doesn't.

Using SFINAE wasn't an option in C++98, so most standard library implementations (which were written long before Expression SFINAE was available) use two separate implementations.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
-1

By the assumption that set should change the value of the referenced object and not the reference itself:

template <class T>
struct wrapper
{
    wrapper(T& x) : reference{ x } {}

    template<typename U> 
    wrapper(U& x) : reference{ x } {} //<- Possible answer for question 3

    //template<typename U>
    //wrapper(U& x) = delete; //<- Possible answer for question 3

    void set(const T& x) const { reference = x; } //<- Answer for question 2
    T& get() const { return reference; }

    T& reference;
};

template <class T>
using const_wrapper = const wrapper<T>; //<- Answer for question 1


int main()
{
    //Testing const_wrapper
    char a;
    const_wrapper<char> b(a);
    b.set('a');

    //Testing narrowing cast
    int i = 42; 
    wrapper<const char> w(i);   //error C2397: conversion from 'int' to 'const char' requires a narrowing conversion
                                //error C2280 : 'wrapper<const char>::wrapper<int>(U &)' : attempting to reference a deleted function
    //w.set('b'); //error C3490: 'reference' cannot be modified because it is being accessed through a const object
}

For your last question I can't give you a satisfying answer. This is pretty much an implemention detail. I can't think of any general case where iterator and const_iterator are not at least inheriting one another - the non-const-version extending the const version.

Update: The last part might be a MSVC specific implementation detail. I still find it reasonable to inherit iterator from const_iterator, yet this should be pointed out.

Simon Kraemer
  • 5,700
  • 1
  • 19
  • 49
  • I can't think of any cases where `const_iterator` inherits from `iterator`. – Jonathan Wakely Dec 08 '15 at 16:57
  • @JonathanWakely The other way around. "the non-const-version extending the const version." So `iterator` inheriting from `const_iterator` – Simon Kraemer Dec 08 '15 at 17:37
  • I have never seen that done either, so I can think of **many** cases where they "are not at least inheriting one another". – Jonathan Wakely Dec 09 '15 at 12:48
  • @JonathanWakely e.g. the MSVC implementation of `std::string::iterator`. The types are defined as `typedef typename _Mybase::iterator iterator;` and `typedef typename _Mybase::const_iterator const_iterator;`. `_MyBase` is `_String_alloc` and there we have `typedef _String_iterator<_String_val<_Val_types> > iterator;` and `typedef _String_const_iterator<_String_val<_Val_types> > const_iterator;`. And `_String_iterator` is defined as `template class _String_iterator : public _String_const_iterator<_Mystr>` – Simon Kraemer Dec 09 '15 at 15:03
  • @JonathanWakely Also it does make sense to make `iterator` inherit `const_iterator` so you can pass an object of type `iterator` as parameter where a `const_iterator` is expected. And you don't have duplicate code. You just have to add the non-const functions. – Simon Kraemer Dec 09 '15 at 15:05
  • @JonathanWakely This might of course be Microsoft specific detail. I will change the last part accordingly. – Simon Kraemer Dec 09 '15 at 15:42
  • Yes, that's definitely specific to that implementation, and not common to all standard library implementations, or other container libraries in general. – Jonathan Wakely Dec 09 '15 at 21:45
  • @JonathanWakely Good to know. Is there any reason this isn't done by default? – Simon Kraemer Dec 09 '15 at 21:56
  • @SimonKraemer: Because it's an implementation detail and different implementations are implemented differently. – Nicol Bolas Dec 13 '15 at 04:40