If I remember correctly, this is actually a big fat wart in the modern C Standard. It's impossible to implement strtod without an explicit cast. There was a huge amount of discussion about this, back in the day, with radical proposals being made for bizarre extensions to the language to make it possible to write strtod "correctly". But the cures were all worse than the disease, so in the end, the radical proposals were not adopted, and the result it that it's tricky (but not impossible) to write strtod and the like.
In your implementation, you will typically have a pointer p that points to the first character you didn't parse. Since p's initial value was your input string, which was const char *, your p will typically be a const char *, too. (And this is fine, because you don't intend to use p to modify the string as you parse it.) But when it comes time to set endp, you're simply going to have to use a cast:
*endp = (char *)p;
It feels lousy to be "casting away constness" like this, but it's really the only way, and it's actually perfectly legal, as Eric P. explains in another answer here.
(Another possibility is *(const char **)endp = p;, but it's more typing, even more sketchy-looking, and it turns out not strictly legal.)
Normally the rule is that explicit casts like this are poor form. Normally the recommendation is to find a way to not need the explicit cast. And although the general rule is a good one, this is an exception, pure and simple. Based on everything else that's going on, the conclusion is that you need this cast here, and if you try to get by without it, you end up having to do something even worse elsewhere.
In answer to a question in a comment, the reason we can't make endp be a const char ** is that it makes things too inconvenient on the caller. The caller might be using pointers that are not const-qualified. Now, if the caller has
char *str = "123.456xyz";
and then calls
double d = strtod(str, NULL);
this is fine: it's okay to pass a regular char * to a function that expects const char *. But if the caller wants to get the end pointer back, and additionally declares
char *endp;
and then calls
double d = strtod(str, &endp);
and if strtod were declared as
double strtod(const char *, const char **);
it turns out it wouldn't work. You can pass a char * to a function that expects a const char *, but you can not pass a char ** to an function that expects a const char **. The explanation for why you can't is rather obscure. There's a sort-of-coherent explanation in the C FAQ list.