Matthias Kauer
Matthias Kauer

Reputation: 10437

C++: setting constness of pointer based on template parameter

I have a struct that manages 'views' into an array of variable type. The purpose of this is to provide a unified state vector for ODE simulation while at the same time working on individual segments of this vector from several other classes in an organized fashion. If this triggers a design pattern in your mind, please let me know.

My issue is that the first implementation ContainerHolderFirst, using Cont::pointer does not compile for const arrays. My next attempt with std::conditional, mixing in Cont::const_pointer still doesn't work. Only the third attempt with std::conditional and modification of Cont::value_type compiles (and seems to work in my larger project). My questions are the following:

Full C++11 code follows:

Update1: Fixing ContainerHolderSecond. It does compile with correct initialization. Also added ContainerHolderBarry suggested by Barry using decltype and declval. This leaves the question whether any of the approaches are preferred? Will they lead to performance differences? They should all compile to the same object, no?

#include <iostream>
#include <array>

template <typename Cont>
class ContainerHolderFirst {
    Cont& data_;

    const static size_t offset_ = 1;
    typename Cont::pointer data_view;
 public:
    ContainerHolderFirst(Cont& data) : data_(data), data_view(&data[offset_]) {}
};

template <typename Cont>
class ContainerHolderSecond {
    using Pointer = typename std::conditional<std::is_const<Cont>::value,
          typename Cont::const_pointer,
          typename Cont::pointer>::type;
    Cont& data_;

    const static size_t offset_ = 1;
    Pointer data_view;

 public:
    ContainerHolderSecond(Cont& data) : data_(data), data_view(&data[offset_]) {}
};

template <typename Cont>
class ContainerHolderBarry {
    using Pointer = decltype(&std::declval<Cont&>()[0]);
    Cont& data_;

    const static size_t offset_ = 1;
    Pointer data_view;

 public:
    ContainerHolderBarry(Cont& data) : data_(data), data_view(&data[offset_]) {}
};


int main() {
    using namespace std;
    array<int, 2> my_array;
    ContainerHolderFirst<array<int, 2>> holder(my_array); // works

    const array<int, 2> const_array{5,7};
    // ContainerHolderFirst<const array<int, 2>> const_holder(const_array);
    /* error: invalid conversion from 'const value_type* {aka const int*}' to 'std::array<int, 2ull>::pointer {aka int*}' [-fpermissive] */

    ContainerHolderSecond<array<int,2>> second_holder(my_array); // works!
    ContainerHolderSecond<const array<int,2>> const_holder(const_array); //updated; works as well; awkward

    ContainerHolderThird<array<int,2>> third_holder(my_array); // still works
    ContainerHolderThird<const array<int,2>> third_const_holder(const_array); //finally compiles as well

    ContainerHolderBarry<array<int,2>> barry_holder(my_array);
    ContainerHolderBarry<const array<int,2>> barry_const_holder(const_array);
}

Upvotes: 1

Views: 145

Answers (2)

ecatmur
ecatmur

Reputation: 157344

The only problem with ContainerHolderSecond is that you're using it incorrectly:

ContainerHolderSecond<array<int,2>> const_holder(const_array);
//                    ^-- insert "const" here

As for ContainerHolderFirst, the reason that array<T, N>::pointer is the same type as (array<T, N> const)::pointer is that there is no automatic way to determine where the const qualification should be added to the nested type, and there is no language facility to describe this (that is, we don't have const-qualified typedefs or type aliases).

Upvotes: 1

Barry
Barry

Reputation: 302827

You're making this unnecessarily difficult on yourself. If you want the type of &cont[offset], just ask for the type of that expression. Use std::declval along with decltype:

template <typename Cont>
class ContainerHolder {
    using Pointer = decltype(&std::declval<Cont&>()[0]);
    ...
};

Upvotes: 1

Related Questions