15

The following is a toy example

The class student has a std::array<char, 15> called name and an integer age. A student has a member function called encode that calls a global template function encode using name.size() as a template parameter.

The code is shown below:

//main.cpp
#include <iostream>
#include <array>

template <unsigned long num1>
unsigned long encode(unsigned long num2){
    return num1 + num2;
}


struct student {
    std::array<char, 15> name;
    int age;
    student(const std::array<char, 15>& name, int age):
        name(name),
        age(age)
    {}
    unsigned long encode(){
        return ::encode<name.size()>(age);
    }
};

int main(){
    std::array<char, 15> name = {"Tim"};
    student Tim(name, 17);
    std::cout << Tim.encode();
}

However, this produces the following compiler error

>g++ main.cpp -std=c++11

main.cpp: In member function 'long unsigned int student::encode()':
main.cpp:22:43: error: use of 'this' in a constant expression
   22 |                 return ::encode<name.size()>(age);
      |                                           ^
main.cpp:22:45: error: no matching function for call to 'encode<((student*)this)->student::name.std::array<char, 15>::size()>(int&)'
   22 |                 return ::encode<name.size()>(age);
      |                        ~~~~~~~~~~~~~~~~~~~~~^~~~~
main.cpp:9:15: note: candidate: 'template<long unsigned int num1> long unsigned int encode(long unsigned int)'
    9 | unsigned long encode(unsigned long num2){
      |               ^~~~~~
main.cpp:9:15: note:   template argument deduction/substitution failed:
main.cpp:22:45: error: use of 'this' in a constant expression
   22 |                 return ::encode<name.size()>(age);
      |                        ~~~~~~~~~~~~~~~~~~~~~^~~~~
main.cpp:22:42: note: in template argument for type 'long unsigned int'
   22 |                 return ::encode<name.size()>(age);

Is it required I do ::encode<15>(age) to solve this problem, because I thought one of the major benefits of a std::array is being able to carry a size rather than having to store the size in some extra variable (or hardcoding the size).

g++ version: 14.1.0

10
  • 1
    "has a std::array<char, 20> called name" - why...? Falsehoods Programmers Believe About Names Commented Dec 29, 2024 at 16:49
  • 1
    @ThomasWeller its just an example i thought of that demonstrates the problem I am having in a short manner, sorry Commented Dec 29, 2024 at 16:50
  • 1
    Side note : for names you should just use std::string (unless you are on firmware and can't use dynamic memory allocation) Commented Dec 29, 2024 at 16:55
  • 1
    Also since you're not encoding at compile time, there is no need for a template function here at all Commented Dec 29, 2024 at 16:56
  • 1
    Fair Point, I am going to think of a better example now,for this problem Commented Dec 29, 2024 at 16:57

3 Answers 3

16

I dislike including the <tuple> header just to use std::tuple_size_v.

I think you can also express the same using

return ::encode<decltype(name){}.size()>(age);
  • decltype(name) gives you the type, without the need of the this pointer
  • {} creates an instance
  • .size() gives you the size of that instance

Since this is constexpr, this evaluates to 15 at compile time and there's no actual overhead for the creation of the temporary instance.

Sign up to request clarification or add additional context in comments.

1 Comment

The std::tuple_size overload for arrays is defined in the <array> header though.
14

You can get the value of N from the array using std::tuple_size<std::array> like:

unsigned long encode(){
    return ::encode<std::tuple_size_v<decltype(name)>>(age);
}

This can be seen working in this live example.

9 Comments

That's nice, but why so difficult? OP's code looks like what I want to write. Why can't I write it? Yes, the error says I can't write it because of this. But it seems that this can be removed from the equation.
@ThomasWeller name.size() requires the use of this, as it is an implicit hidden variable to the function call. You need to not rely on the instance and instead rely on the type since std::array doesn't provide a convenient way of extracting the value of N.
If I add auto N = Tim.name.size();, the compiler optimizes this to mov QWORD PTR [rbp-8], 15, so there's no this involved after optimization. Why can't it do that for the template parameter?
@ThomasWeller It's not allowed to. template parameters are required to be compile time constants and as is name.size() is not a constant expression in the OP's code. YOU can do this if the code is constexpr for instance: coliru.stacked-crooked.com/a/0e79de5605bbb588
@MrOnlineCoder Because encode is not constexpr and if it was then it would only work if the object that called encode is also constexpr.
|
11

Using name.size() requires to access this (and via it the member name) but this fails to compile because this is not a constexpr context.

Instead you should use the type of the std::array name which already implies the size.
For this you can use std::tuple_size (which has a specialization for std::array),
possibly with its helper variable template std::tuple_size_v:

#include <tuple>

// ...

unsigned long encode() {
    return ::encode<std::tuple_size_v<decltype(name)>>(age);
}

Note the usage of decltype to get the type of the std::array from the member name.

Live demo.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.