Juliean

C++ Char pointer/array type ambiguity

Recommended Posts

Juliean    7068

So I'm trying to design a function that acts differently, based on whether it is passed a const char/wchar_t array, or a const char/wchar_t*:

template<typename Char, size_t Length>
size_t stringLength(const Char(&pString)[Length])
{
  return Length - 1;
}

template<typename Char>
size_t stringLength(const Char* pType)
{
  return strlen(pType);
}

const char* pTest = "Test";
stringLength(pTest); // => should return 4
stringLength("Test"); // => should return 4 as well

The problem is that the last line doesn't compile, saying that the function-call is ambigous between both overloads, even though it correctly identifies the argument as "const char [8]", which works as intended if I remove the "const Char* pType" overload.

Now, why is this ambigous? As far as I understand it, the upper function should be a closer match to the argument list and thus be selected. Is there anything I have to/can do to make that work? (I'm on MSVC 2017)

Share this post


Link to post
Share on other sites
trjh2k2    416

Correct me if I'm wrong, but it's ambiguous because char* and char[] are the same type.  Both of them are just pointers to the first character in an array of characters.

Share this post


Link to post
Share on other sites
Juliean    7068
5 minutes ago, trjh2k2 said:

Correct me if I'm wrong, but it's ambiguous because char* and char[] are the same type.  Both of them are just pointers to the first character in an array of characters.

Well, from what I understand, a size-qualified "char[X]"-array isn't exactly the same type as a char*.

For example, you can convert the char[X] to a char*, but not the other way around:

char array[4] = {};
char* pointer;

pointer = array; // works
array = pointer; // doesn't

Also the first function can't be called with char*, and will have the correct array-size if called with a char[X]. So all of this ad least made me belive that they are different types; though obviously the compiler assumes they are ambigous, maybe for the reason you wrote.

I might have another idea that I'm going to try out though, just remembered that there was a std-trait to find out if a type is an array & to get the arrays extent... though thats going to result in more messy template code, so if someone found an easier solution I'd still appreciate it :)

 

Share this post


Link to post
Share on other sites
trjh2k2    416

The problem I think is not that you can't pass char[x] to a char*, but that you CAN pass "text" to both.

 

What are you trying to accomplish?  This doesn't look like a good way to check the length of a string.

Share this post


Link to post
Share on other sites
Juliean    7068
24 minutes ago, trjh2k2 said:

The problem I think is not that you can't pass char[x] to a char*, but that you CAN pass "text" to both.

Yes, this is true, yet from how I can see it this only happens via cast (char[x] => char*), so under normal overload resolution rules, I still don't see how it would be any different to:
 

void func(int x)
{
}

void func(float x)
{
}

func(0); // calls "func(int x)"

I mean you're obviously right about what happens, it just feels wrong to me :>

24 minutes ago, trjh2k2 said:

What are you trying to accomplish?  This doesn't look like a good way to check the length of a string.

Its actually being used as an optimization string-length generation as part of my custom StringView-class:

template<size_t Length>
constexpr BaseStringView(StaticString<Length> pString) : // const Type(&)[Length]
	BaseStringView(pString, StringLength<Length>(pString))
{
};

template<size_t Length>
constexpr BaseStringView(DynamicString<Length> pString) : // Type(&)[Length] => prevents issues with user-handled char-buffers
	BaseStringView(pString, StringLength(pString))
{
};

I know its technically not 100% safe, but I made sure that it doesn't break anything for me; and since I'm using a string-view I'm already in not-safe territory. As you can see I've got a second overload that gets called when I'm passing in an actual "char array[X];" that is filled from ie. an windows-API method. The actual reason why I'd need the "const char*" overload is that right now this would instead call the "const std::string&" overload, thus creating an unncessary copy & a dangling pointer (if the view is actually locally stored).
Not that it happens that often, most of my codebase has now been ported to use StringView & size-qualified strings, but there's always some places where this could still happen.

Share this post


Link to post
Share on other sites
NajeNDa    119

As you said before, char[] and char* are just the same, but depending on what you want to do/need you have to cast. And yeah, you can cast from char[] to char* this way:

char array[4] = {'a','b','c','d'};
char* charPointer = nullptr;

charPointer = &array[0];

You have to point your pointer to the beginning of your char array, there is no direct assignment. You might overload operators if you really use that much char-pointer assignment.

Edited by NajeNDa

Share this post


Link to post
Share on other sites
frob    44908

The reference trick in the StackOverflow site is the one I've seen several times over the years:

template<typename T> void f(T* const& c){ std::cout << "pointer\n"; }
template<typename T, size_t N> void f(T(&)[N]){ std::cout << "array\n"; }

Even though you as a programmer don't know, your compiler hitting the code can potentially know.  It works if the parameter being directly passed in is known to be a fixed-length array. If it goes through a single indirection to a pointer and the indirection isn't optimized a way, then the information is lost and the compiler will deduce it as a pointer. If it goes through an indirection and the indirection gets optimized away it can still deduce it correctly.  

So even though that can work in some cases, it won't work in all cases after indirections.

 

The useful cases are almost non-existent.

 

As for the original problem here on the thread where you're trying to avoid taking the string length, that's not much of a benefit.  You're trying to simplify the interface, but instead you are adding complexity by having an additional entry.  Instead of having only one interface:  (buffer, size), you've now got two interfaces: (buffer, size) and (fixed-length-array).  If the writer knows they've got a fixed array they can use (buffer, sizeof(buffer)). If the writer knows they've got a more traditional buffer they can use (buffer, buflen). They know the single interface is there and they need to use it.

Imagine if the C language used that in their interfaces.  You'd have the current set of twenty-ish memory functions like memmov, memcpy, memcmp, and a duplicate version of all the functions for fixed-length arrays.

 

Share this post


Link to post
Share on other sites
trjh2k2    416

I guess what I meant to say is that rolling your own ways to check string length like this reads to me like a code smell / design smell kind of scenario.  If you created the array, you already know the size, so you can pass it around if you need it.

Share this post


Link to post
Share on other sites
Juliean    7068
1 hour ago, Kylotan said:

 

41 minutes ago, frob said:

The reference trick in the StackOverflow site is the one I've seen several times over the years:


template<typename T> void f(T* const& c){ std::cout << "pointer\n"; }
template<typename T, size_t N> void f(T(&)[N]){ std::cout << "array\n"; }

Ah, yeah, thats what I've been looking for!

41 minutes ago, frob said:

The useful cases are almost non-existent.

 

24 minutes ago, trjh2k2 said:

I guess what I meant to say is that rolling your own ways to check string length like this reads to me like a code smell / design smell kind of scenario.  If you created the array, you already know the size, so you can pass it around if you need it.

Well, I should have been a bit more specific about my use-case: As I've mentioned I'm using my own StringView class, akin to std::experimental::basic_string_view.

Now that means that functions may have a signature as such:

bool Node::HasNode(sys::StringView strName) const
{
  return m_mNodes.count(strName) != 0;
}

where it would have been eigther "const std::string&" (for me), or possible "const char*" / "const char*, size_t" before. This has many benefits, as such std::string_view has been proposed, but thats not the point of this post. Now in my code, I might use those functions as such:
 

const auto strName = node.Attribute("name")->GetValue();
widget.SetName(strName.ToString());

const auto isVariable = node.HasNode("IsVariable");
widget.SetIsVariable(isVariable);
	
const auto visibilty = core::VariableLoader::FromAttribute<Visibility>(node, "Visibility");
widget.SetVisibility(visibilty);

const auto isEnabled = !node.HasNode("Disabled");
widget.SetEnabled(isEnabled);

Not the every function above takes a sys::StringView. And thats pretty much where I applied my optimization. std::string_view would take a const char*, and call strlen. My StringView-constructor can take a static char-array, and directly deduce the size from this - thats the reason why I don't wanna do it by hand even though I technically "know" the strings size, its simple convenience so that I can call all those functions with string literals, but without having to take a copy or determine the size.

41 minutes ago, frob said:

As for the original problem here on the thread where you're trying to avoid taking the string length, that's not much of a benefit.  You're trying to simplify the interface, but instead you are adding complexity by having an additional entry.  Instead of having only one interface:  (buffer, size), you've now got two interfaces: (buffer, size) and (fixed-length-array).  If the writer knows they've got a fixed array they can use (buffer, sizeof(buffer)). If the writer knows they've got a more traditional buffer they can use (buffer, buflen). They know the single interface is there and they need to use it.

As you should see in my explanation, the function I proposed isn't really going to be part of an interface, its just an additional constructor for my StringView-class that internally calls it. I don't know if that makes it any better in your book, but I do see a compelling case for handling string-literals the way I do. Also the purpose of StringView is to offer a unified interface from many types (std::string, const char*, const char*+size) to a single const char*, size_t-pair. So I'd say my general notion is not totally wrong - the only difference I make is instead of treating every "const char*" as a nul-terminated string, I'm making a differentiation between static string-literals as part of a small optimization.
 

41 minutes ago, frob said:

Imagine if the C language used that in their interfaces.  You'd have the current set of twenty-ish memory functions like memmov, memcpy, memcmp, and a duplicate version of all the functions for fixed-length arrays.

Sure, adding 3-4 overloads for the same functions is surely overkill, I agree on that (in my case I should have mentioned how its intented to being used), but since we are talking about C-API functions - as you can read in my other thread:

there's actually a lot of issues going forward with modern C++ now that most C-style API functions only take nul-terminated C-strings; which wasn't a problem before but now with string_view this is actually limiting its usefulness. So I'd personally rather have atoi(const char*) and atoi(const char*, size_t) than being forced to make sure my strings are nul-terminated... but I thankfully don't have to support a large userbase with my API, so my expertise in that regard is rather limited.

EDIT: Anyways, the suggested "tricks" seem to work, even though for some reason I have to add a template type to my template-class ctor for it to work:

template<typename Type>
class StringView
{
  	template<typename Char, CheckIsCharPointer<Char> = 0>
	BaseStringView(const Char* const& pString) : // still ambigous if I just use "Type" directly
		BaseStringView(pString, StringLength(pString))
	{
	};
  
   template<size_t Length>
   constexpr BaseStringView(DynamicString<Length> pString) :
   BaseStringView(pString, StringLength(pString))
   {
   };
}

But the problem seems solved, so thanks to all for helping me solve the problem :) I'm still rather happy to discuss the issues revolving around this; I just recently started to work with string_view so its certainly good to get more input on it.

Share this post


Link to post
Share on other sites
trjh2k2    416
1 minute ago, Juliean said:

My StringView-constructor can take a static char-array, and directly deduce the size from this

Again, maybe I'm misunderstanding, but you don't know the size of the string this way- you know the size of the array that holds your string.  If your array of characters has a zero anywhere but at the end, your resulting view will have more chars than the length of the text you're giving it.

Share this post


Link to post
Share on other sites
Juliean    7068
12 minutes ago, trjh2k2 said:

Again, maybe I'm misunderstanding, but you don't know the size of the string this way- you know the size of the array that holds your string.  If your array of characters has a zero anywhere but at the end, your resulting view will have more chars than the length of the text you're giving it.

This could potentially happen, yes. Unless I'm mistaken, this realistically shouldn't happen though:

The key lies in "const char[X]". How do you generate such a type? You could manually declare it, sure, and if someone would go:

const char test[32] = "Test";
const char testing[] = "Test\0ing"; // ... well

Okay, now the StringView says it points to a 32-character long string which should only have 4 characters; and the other one has a \0 manually put int he middle... But outside of that, the only way I can see that you can aquire such a type is by declaring an actual string:

const char test[] = "Test"; // now its fixed
constexpr char constTest[] = "Test";

// all fine
StringView(test);
StringView(constTest);
StringView("Test");

Since you cannot modify the content of a const char[X], I fail to see any other case where you'd end up with what you described. Even if, it would be trivial to check if (strlen == Size) for debug-builts, to rule out the one case I mentioned.

Now what you are thinking about is probably something like this:

char buffer[MAX_PATH];
GetCurrentDirecoty(buffer, MAX_PATH. 0);

StringView(buffer); //uh-oh

Though in this case, as I've said I've simply added a second overload that will be called if you pass in a "char [X]" as opposed to a "const char[X]", and that will actually call strlen.

I mean, it seems pretty obvious to me - but am I missing something? I really cannot think about how else one would realistically create a "const char[X]" type that has the nul-terminator not at the end. Maybe through multiple layers of functions that all take "const char[X]" where someone passes in such a "char buffer[256]", but thats besides what I consider a realistic use-case, in regards to how the StringView-class is being used.

Share this post


Link to post
Share on other sites
swiftcoder    18427

I'm curious *why* you think it is useful to implement this just to elide the strlen() invocation, when your compiler already optimises away strlen() on string constants?

Observe the following C++ program:


#include <cstdio>
#include <cstring>

class IndirectLen {
public:
    IndirectLen(const char *s) : l(strlen(s)) {}

    const long l;
};

int main() {

    const char *name = "foo";

    printf("%ld\n", IndirectLen(name).l);

    return 0;
}

And observe the generated assembly when Clang compiles it:

	.section	__TEXT,__text,regular,pure_instructions
	.macosx_version_min 10, 12
	.globl	_main
	.p2align	4, 0x90
_main:                                  ## @main
	.cfi_startproc
## BB#0:
	pushq	%rbp
Ltmp0:
	.cfi_def_cfa_offset 16
Ltmp1:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
Ltmp2:
	.cfi_def_cfa_register %rbp
	leaq	L_.str.1(%rip), %rdi
	movl	$3, %esi
	xorl	%eax, %eax
	callq	_printf
	xorl	%eax, %eax
	popq	%rbp
	retq
	.cfi_endproc

	.section	__TEXT,__cstring,cstring_literals
L_.str.1:                               ## @.str.1
	.asciz	"%ld\n"


.subsections_via_symbols

The compiler was smart enough to not only replace strlen() with a constant, but to elide the entire containing class...

Share this post


Link to post
Share on other sites
Juliean    7068
22 hours ago, swiftcoder said:

I'm curious *why* you think it is useful to implement this just to elide the strlen() invocation, when your compiler already optimises away strlen() on string constants?

Uhh... you gave part of the answer in your question though: I didn't know that the compiler was even able to do that in the first place :) I just checked if my MSVC-compiler does that too, and in more complex environments, but yeah, seems like this is something pretty basic optimizationwise. Good to know, reduces code-size quite a bit & saves me from further trouble with that kind of stuff. Thanks!

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now


  • Similar Content

    • By ccuccherini
      I'm running to some issues with a random number generator that I am trying to create to test what type of attack an enemy will use (weapon or magic) and whether or not they land a hit with the attack.  What I want it to do is generate a random number number for the type of attack, then generate a separate random number for if the hit lands or not.  What actually happens is it is generating a different number each iteration, but the same number for each item.  So for instance, if the random number is a 6, both attack type and if it hit is a 6, if it's a 7 both are 7, etc.   I'm almost certain the issue is within how I'm using time in conjunction with this, but everything else I've found hasn't been helpful.  I've tried doing one function, but had the same issue and switched to two functions in the hopes that would help, but it didn't and now I'm not quite sure what to try next.
      Here is the code I have so far:
      // Example program #include <iostream> #include <string> #include <stdlib.h> #include <ctime> using namespace std; int getRandAttack(); int getRandHit(); int main() { int randomAttack; int randomHit; randomAttack = getRandAttack(); cout<<randomAttack<<endl; //used to ensure same number isn't given every time //decides what happens based on what number was given if (randomAttack <= 5) { cout<<"Enemy used a weapon!"<<endl; }else if (randomAttack > 5) { cout<<"Enemy used magic!"<<endl; } randomHit = getRandHit(); cout<<randomHit<<endl; //used to ensure same number isn't given every time //decides what happens based on what number was given if (randomHit <= 5) { cout<<"The attack missed!"; }else if (randomHit > 5) { cout<<"The attack hit!"; } } //gets random number to determine enemy attack type int getRandAttack () { int random; srand (time(NULL)); random = rand() % 10 + 1; return random; } //gets random number to determine if enemy attack lands a hit int getRandHit () { int random; srand (time(NULL)); random = rand() % 10 + 1; return random; } I know there is an easier way to do this that involves classes, but the online compiler I'm messing around with right now doesn't seem to support extra files, so I'm making functions work instead.  Any help and lead in the right direction would be greatly appreciated.
    • By khawk
      Urho3D 1.7 has been released. The release for the open source, cross-platform 2D/3D game engine includes a number of new features and bug fixes, including new IK support, AppleTV platform support, WebAssembly support, improved font rendering, better integration with Bullet and Box2D, renderer improvements, and more.
      Download the release and view the full changelog at https://urho3d.github.io/releases/2017/08/19/urho3d-1.7-release.html#changelog.
       

      View full story
    • By khawk
      Urho3D 1.7 has been released. The release for the open source, cross-platform 2D/3D game engine includes a number of new features and bug fixes, including new IK support, AppleTV platform support, WebAssembly support, improved font rendering, better integration with Bullet and Box2D, renderer improvements, and more.
      Download the release and view the full changelog at https://urho3d.github.io/releases/2017/08/19/urho3d-1.7-release.html#changelog.
       
    • By ChobitsTheZero
      I have a problem understanding how excatly the intersection test when testing ray against plane, sphere and OBB. I have a presentation of an assignment and I need to be able to explain how the code I have written works. But I dont really understand it, even though I have searched over the internet and read through a lot of sites telling how it works. I still dont really understand it, im hoping that someone can give me a dummy explaination of it that is easy to understand. I'll provide the code in question down below, all help is appreciated. I just really wanna understand how this stuff works fully.
      void GiantSphere::test(Ray & ray, HitData & hit) { float b = ray.d.Dot(ray.o - this->center); float j = (ray.o - this->center).Dot(ray.o - this->center) - pow(this->radius,2); float intersectCalc = b*b - j; if (intersectCalc >= 0) { float t = -b - sqrt(intersectCalc); if (hit.t == -1 || hit.t > t) { hit.t = t; hit.lastShape = this; hit.color = this->c; hit.lastNormal = this->normal(ray.o+ray.d*t); } } } Vec GiantSphere::normal(Vec & point) { Vec temp = point - this->center; temp.Normalize(); return temp; } GiantSphere::GiantSphere(Vec _center, float _radius, Color _color) { this->center = _center; this->radius = _radius; this->c = _color; } void GiantPlane::test(Ray & ray, HitData & hit) { float d = this->n.Dot(this->point); float t = (d - this->n.Dot(ray.o)) / this->n.Dot(ray.d); if (t > 0.0001f) { if (hit.t == -1 || hit.t > t) { hit.t = t; hit.lastShape = this; hit.color = this->c; hit.lastNormal = this->n; } } } Vec GiantPlane::normal(Vec & point) { point = n; return point; } GiantPlane::GiantPlane(Vec normal, float _d, Color color) { this->n = normal; //this->d = _d; this->point = this->n*_d; this->c = color; } void GiantOBB::test(Ray & ray, HitData & hit) { float tMin = -INFINITY; float tMax = +INFINITY; Vec p = Bcenter - ray.o; // Bcenter is the center of the OBB. Ray.o is the origin of the ray. Vec arr[3] = { Bu,Bv,Bw }; // Direction/base vectors. (1,0,0), (0,1,0), (0,0,1) for (auto& i : arr) { float e = i.Dot(p); float f = i.Dot(ray.d); float q = -e - halfBu; // The distance between the center and each side. 100. float w = -e + halfBu; if (abs(f) > 1e-20f) { float t1 = ((e + halfBu) / f); float t2 = ((e - halfBu) / f); if (t1 > t2) { std::swap(t1, t2); } if (t1 > tMin) { tMin = t1; } if (t2 < tMax) { tMax = t2; } if (tMin > tMax) { return; } if (tMax < 0) { return; } } else if (q > 0 || w < 0) { return; } } if (tMin > 0) { if (hit.t == -1) { hit.t = tMin; hit.lastShape = this; hit.color = c; } else if (tMin < hit.t) { hit.t = tMin; hit.lastShape = this; hit.color = c; } } else if (tMax <= 0) { if (hit.t == -1) { hit.t = tMax; hit.lastShape = this; hit.color = c; } else if (tMax < hit.t) { hit.t = tMax; hit.lastShape = this; hit.color = c; } } } Vec GiantOBB::normal(Vec & point) { Vec arr[3] = { this->Bu,this->Bv,this->Bw }; float halfArr[3] = { this->halfBu,this->halfBv,this->halfBw }; Vec returnValue = Vec(); for (int i = 0; i < 3; i++) { Vec sPlus = this->Bcenter + (arr[i] * halfArr[i]); Vec sMinus = this->Bcenter - (arr[i] * halfArr[i]); if ((point - sPlus).Dot(arr[i]) < 0.0001f && (point - sPlus).Dot(arr[i]) > -0.0001f) { float dot = (point - sPlus).Dot(arr[i]); returnValue = arr[i]; } else if ((point - sMinus).Dot(arr[i] * -1) < 0.0001f && (point - sMinus).Dot(arr[i] * -1) > -0.0001f) { float dot2 = (point - sMinus).Dot(arr[i]); returnValue = arr[i] * -1; } } return returnValue; } GiantOBB::GiantOBB(Vec b, Vec b1, Vec b2, Vec b3, float Hu, float Hv, float Hw, Color _color) { this->Bcenter = b; this->Bu = b1; this->Bv = b2; this->Bw = b3; this->c = _color; this->halfBu = Hu; this->halfBv = Hv; this->halfBw = Hw; } GiantOBB::GiantOBB(Vec b, float Hu, float Hv, float Hw, Color _color) { this->Bcenter = b; this->halfBu = Hu; this->halfBv = Hv; this->halfBw = Hw; this->c = _color; }  
    • By hyyou
      I have recently read T-machine's ECS data structure. (link)
      Its "best" version (4th iteration) can be summarized into a single image (copy from T-machine's web) :-

      This approach also appears in a VDO https://www.youtube.com/watch?v=NTWSeQtHZ9M  , with slide and source code ( https://github.com/CppCon/CppCon2015/tree/master/Tutorials/Implementation of a component-based entity system in modern C++ ) .
      There are many people (including me) believe that the above approach lacks data locality, because Position-1,Position-2,Position-3, ... are not stored in a contiguous array.
      However, many attempts failed to elegantly make it contiguous.  (e.g. stackoverflow's link) 
      I also realized that, to make it contiguous, the cost of query Entity<-->Component is unavoidably significantly higher. (I profiled) I would like to hear that ....
      Is such mega-array (T-machine's 4th) generally better (performance-wise) than storing each type component to its own contiguous array?  (like http://www.randygaul.net/2013/05/20/component-based-engine-design/ )  ? My references (those links) are quite old.   Now, are there any others better approaches? (If you happen to know) I know that its depend on data access pattern (i.e. cache miss).  I still want to hear more opinion/argument about it, because I may need to rewrite a large part of my engine to make it support 30,000+ entity (personal greed).    
      Thank.
       
       
  • Popular Now