0

I want to have a class with a constant pointer to a function as a member. However, I'm instead creating functions with constant return values (a strange feature I just run into while debugging).

The troublesome declarations are:

// ...

template<typename T, typename ComparableValue>
class TimestampedValueHeap {
    public:
        TimestampedValueHeap(
            const ComparableValue (*)(const T&),
            const big_unsigned (*)(const T&)
        );

    protected:
        const ComparableValue (*getValue)(const T&);
        const big_unsigned (*getTimestamp)(const T&);
};

// ...

template<typename T, typename ComparableValue>
class TimestampedValueMinHeap : TimestampedValueHeap<T, ComparableValue> {
    public:
        TimestampedValueMinHeap(
            const ComparableValue (*)(const T&),
            const big_unsigned (*)(const T&)
        );
};

// ...

Here's the minimal WORKING example without const's. I'm using C++14.

#include <utility>

using namespace std;

typedef unsigned long long big_unsigned;

template <typename T, typename U>
T getFirst(const pair<T, U>& x) {
  return x.first;
}

template<typename T, typename U>
U getSecond(const pair<T, U>& x) {
    return x.second;
}

// ==================================================
// TimestampedValueHeap
// ==================================================

template<typename T, typename ComparableValue>
class TimestampedValueHeap {
    public:
        TimestampedValueHeap(
            ComparableValue (*)(const T&),
            big_unsigned (*)(const T&)
        );

    protected:
        ComparableValue (*getValue)(const T&);
        big_unsigned (*getTimestamp)(const T&);
};

template<typename T, typename ComparableValue>
TimestampedValueHeap<T, ComparableValue>::TimestampedValueHeap(
    ComparableValue (*getValue)(const T&),
    big_unsigned (*getTimestamp)(const T&)
): getValue(getValue), getTimestamp(getTimestamp) {
}

// ==================================================
// TimestampedValueMinHeap
// ==================================================

template<typename T, typename ComparableValue>
class TimestampedValueMinHeap : TimestampedValueHeap<T, ComparableValue> {
    public:
        TimestampedValueMinHeap(
            ComparableValue (*)(const T&),
            big_unsigned (*)(const T&)
        );
};

template<typename T, typename ComparableValue>
TimestampedValueMinHeap<T, ComparableValue>::TimestampedValueMinHeap(
    ComparableValue (*getValue)(const T&),
    big_unsigned (*getTimestamp)(const T&)
): TimestampedValueHeap<T, ComparableValue>::TimestampedValueHeap(getValue, getTimestamp) {
}

int main() {
    const TimestampedValueMinHeap<pair<int, big_unsigned>, int> minHeap(
        getFirst<int, big_unsigned>,
        getSecond<int, big_unsigned>
    );
    return 0;
}

Here's the BROKEN example with the added const's.

#include <utility>

using namespace std;

typedef unsigned long long big_unsigned;

template <typename T, typename U>
T getFirst(const pair<T, U>& x) {
  return x.first;
}

template<typename T, typename U>
U getSecond(const pair<T, U>& x) {
    return x.second;
}

// ==================================================
// TimestampedValueHeap
// ==================================================

template<typename T, typename ComparableValue>
class TimestampedValueHeap {
    public:
        TimestampedValueHeap(
            const ComparableValue (*)(const T&),
            const big_unsigned (*)(const T&)
        );

    protected:
        const ComparableValue (*getValue)(const T&);
        const big_unsigned (*getTimestamp)(const T&);
};

template<typename T, typename ComparableValue>
TimestampedValueHeap<T, ComparableValue>::TimestampedValueHeap(
    const ComparableValue (*getValue)(const T&),
    const big_unsigned (*getTimestamp)(const T&)
): getValue(getValue), getTimestamp(getTimestamp) {
}

// ==================================================
// TimestampedValueMinHeap
// ==================================================

template<typename T, typename ComparableValue>
class TimestampedValueMinHeap : TimestampedValueHeap<T, ComparableValue> {
    public:
        TimestampedValueMinHeap(
            const ComparableValue (*)(const T&),
            const big_unsigned (*)(const T&)
        );
};

template<typename T, typename ComparableValue>
TimestampedValueMinHeap<T, ComparableValue>::TimestampedValueMinHeap(
    const ComparableValue (*getValue)(const T&),
    const big_unsigned (*getTimestamp)(const T&)
): TimestampedValueHeap<T, ComparableValue>::TimestampedValueHeap(getValue, getTimestamp) {
}

int main() {
    const TimestampedValueMinHeap<pair<int, big_unsigned>, int> minHeap(
        getFirst<int, big_unsigned>,
        getSecond<int, big_unsigned>
    );
    return 0;
}

You should get a compilation error such as:

No matching constructor for initialization of 'const TimestampedValueMinHeap<pair<int, big_unsigned>, int>' (aka 'const TimestampedValueMinHeap<pair<int, unsigned long long>, int>')

However, it works if you simply cast the arguments.

// ...
int main() {
    const TimestampedValueMinHeap<pair<int, big_unsigned>, int> minHeap(
        (const int (*)(const pair<int, big_unsigned>&)) getFirst<int, big_unsigned>,
        (const big_unsigned (*)(const pair<int, big_unsigned>&)) getSecond<int, big_unsigned>
    );
    return 0;
}
  • The 'thing' that you want to be const is getValue. So put the const closest to getValue. – Matthew M. May 13 '22 at 18:34
  • It is not clear what you want to achieve. My impression is that constant members (of any type) are always problematic. It is always better to make them non-const and protect them in the public interface. – alfC May 13 '22 at 18:35
  • @MatthewM. How would you do it? I'm getting compilation errors when I try `ComparableValue (const getValue*)(const T&)` – José Alvarado Torre May 13 '22 at 18:39
  • 1
    `int (* const getValue)(const T&)` – Matthew M. May 13 '22 at 18:41
  • `int (* getValue)(const T&)` is a function pointer named getValue of type `int (*)(const T&)` Since its the pointer value `getValue` (and not its type) that you want to make const, you put const right up against it. – Matthew M. May 13 '22 at 18:43
  • Since your pointers to functions are all the same they should be static. This also avoids potential issues such as automatic deletion of the default assignment copy caused by const ptrs as members. – doug May 13 '22 at 18:44
  • @doug I intend to pass different functions depending on the use case. I'm not familiar with that issue. Is there a resource for me to read more? Is there a safer way to do this? I'm looking into the std::function or smart pointer solutions. – José Alvarado Torre May 13 '22 at 18:50
  • The only reason to have pointers to functions as class members is if your different functions have exactly the same function signatures. If so you need member ptrs. If not static ones are best because they don't expand your class object size. – doug May 13 '22 at 18:55
  • Never mind. I was too focused on your question title. Looking at your code they are pointers to functions returning consts, not const pointers to functions. The former don't require special handling. – doug May 13 '22 at 19:19

1 Answers1

3

Try doing it in steps, it is more readable also,

        using getValue_ptr_type = ComparableValue (*)(const T&);
        using const_getValue_ptr_type = const getValue_ptr_type;
        const_getValue_ptr_type getValue;

OR use EAST const (my favorite)

        ComparableValue (* const getValue)(const T&);
        big_unsigned (* const getTimestamp)(const T&);

https://godbolt.org/z/z7P6P3sKz


NOTE: @MatthewM. rightly says that it is not a matter of EAST const or const WEST. This the only right way (for one liners).

east const

alfC
  • 14,261
  • 4
  • 67
  • 118
  • Wow, that Compiler Explorer is an amazing resource. Thank you! Function pointer ordering is weird in C++. – José Alvarado Torre May 13 '22 at 18:46
  • Whenever I have to deal with function pointers then a `typedef` or `using` is the first thing I do. The syntax is just unbearable otherwise. As for the second `using` to make it const: I would skip that. `const getValue_ptr_type` is readable enough and the syntax highlighting works better there to point out the constnes. – Goswin von Brederlow May 13 '22 at 18:48
  • @GoswinvonBrederlow, yes, the problem is that you eventually need the exact type of `getValue` any way (which I wouldn't make `const` anyway). – alfC May 13 '22 at 18:49
  • In this case, east const is west const. – Matthew M. May 13 '22 at 18:51
  • BTW, I don't know if it is even possible to do this in one line using const west. Also, I should have say `ComparableValue (* const getValue)(T const&);` to be consistent. – alfC May 13 '22 at 18:53
  • As I said, there is no east vs west const here. This is just 'the way'. – Matthew M. May 13 '22 at 18:56
  • 1
    mmm, ah, ok. I would have said it was clearly east const because the "const" was next to the variable name. But is true that it is not to left of any type either. – alfC May 13 '22 at 18:59
  • You know, I've been coding C++ since its invention... and I honestly never knew about this 'east const vs. west const' feud. Never even heard the terms until a few months ago. *shrug* I personally don't find one more readable than the other. They all look like C++ to me. – Matthew M. May 13 '22 at 19:05
  • 1
    @MatthewM., fair enough. I find putting the const as late as possible (without changing the meaning of course) more clear. Most (all?) of the time that means that qualifiers, such as `&` and `const` remain together `const&` (which the only way for qualified member function and also in our case here of function pointers, e.g. if you try to make constant *reference* pointers. C language had its own feuds, `*` on the type vs `*` on the variable I guess. – alfC May 13 '22 at 19:11