A simple and safe implementation for strnstr (search for a substring in the first n characters of char array)
Design:  The str as an array and searched up to n is inconsistent with string like functions and strstr().  Rather than "the search is limited to the first n characters in the array", I'd also expect characters in str that follow a null character are not searched.   IMO, a design flaw.

Following review assumes str[i] == 0 has no special meaning.
Weak argument name str.  str is the address of an array and maybe not a string.  Calling a potential non-string str conveys the wrong idea.  Suggest src, etc.  When looking for sub-strings, I like needle and haystack.
As the C version does not change str contents, recommend making that const.  "The rationale for the first parameter is not to be const is that you may want to use the return value pointer to modify the array in that location." does not apply.  Just cast char * the return value.  Follow strstr()s tyle.

// From C library.
      char *strstr( const char *s1, const char *s2); 

// Expected signatures: (spaced for clarity)
// C
      char *strnstr(const char *src, const char *substr, size_t n);
// C++
      char *strnstr(      char *src, const char *substr, size_t n);
const char *strnstr(const char *src, const char *substr, size_t n);

Using a name that is close to standard names is tricky.  C reserves name with certain prefixes, etc and so does *nix, etc.  Maybe use CP_strnstr() and an optional #define strnstr CP_strnstr.
Corner case: Returning str with if(0 == substr_len) return str; does not make sense when size == 0.  I'd expect NULL. 
Underflow possible.  The length of the needle may be longer or shorter than the haystack

// add check
if (n + 1 &lt; substr_len) {
  return NULL;
}
pEnd -= (substr_len - 1);



Minor


In debug mode, consider testing against NULL

char *strnstr(char *str, const char *substr, size_t n) {
  assert(str || n == 0);
  assert(substr);