This is how you do:

/*
* Author: Ramon Casero <rcasero@gmail.com>
* Copyright © 2011-2013 University of Oxford
* Version: 0.10.2
*
* University of Oxford means the Chancellor, Masters and Scholars of
* the University of Oxford, having an administrative office at
* Wellington Square, Oxford OX1 2JD, UK. 
*
* This file is part of Gerardus.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details. The offer of this
* program under the terms of the License is subject to the License
* being interpreted in accordance with English Law and subject to any
* action against the University of Oxford being under the jurisdiction
* of the English Courts.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see
* <http://www.gnu.org/licenses/>.
*/

#ifndef MEXINTERRUPT_H
#define MEXINTERRUPT_H

/* mex headers */
#include <mex.h>

/* C++ headers */
#include <iostream>
#include <string>
/*
* utIsInterruptPending(): "undocumented MATLAB API implemented in
* libut.so, libut.dll, and included in the import library
* libut.lib. To use utIsInterruptPending in a mex-file, one must
* manually declare bool utIsInterruptPending() because this function
* is not included in any header files shipped with MATLAB. Since
* libut.lib, by default, is not linked by mex, one must explicitly
* tell mex to use libut.lib." -- Wotao Yin, 
* http://www.caam.rice.edu/~wy1/links/mex_ctrl_c_trick/
*
*/
#ifdef __cplusplus 
  extern "C" bool utIsInterruptPending();
#else
  extern bool utIsInterruptPending();
#endif

/*
* ctrlcCheckPoint(): function to check whether the user has pressed
* Ctrl+C, and if so, terminate execution returning an error message
* with a hyperlink to the offending function's help, and a hyperlink
* to the line in the source code file this function was called from
*
* It is implemented as a C++ macro to check for the CTRL+C flag, and
* a call to function ctrlcErrMsgTxt() inside, to throw the error. The
* reason is that if ctrlcCheckPoint() were a function instead of a
* macro, this would introduce a function call at every iteration of
* the loop, which is very expensive. But then we don't want to put
* the whole error message part inside a macro, it's bug-prone and bad
* programming practice. And once the CTRL+C has been detected,
* whether the error message is generated a bit faster or not is not
* important.
*
* In practice, to use this function put a call like this e.g. inside
* loops that may take for a very long time:
*
*    // exit if user pressed Ctrl+C
*    ctrlcCheckPoint(__FILE__, __LINE__);
*
* sourceFile: full path and name of the C++ file that calls this
*         function. This should usually be the preprocessor
*         directive __FILE__
*
* lineNumber: line number where this function is called from. This
*         should usually be the preprocessor directive __LINE__
*
*/
inline
void ctrlcErrMsgTxt(std::string sourceFile, int lineNumber) {

// run from here the following code in the Matlab side:
//
// >> path = mfilename('fullpath')
//
// this provides the full path and function name of the function
// that called ctrlcCheckPoint()
int nlhs = 1; // number of output arguments we expect
mxArray *plhs[1]; // to store the output argument
int nrhs = 1; // number of input arguments we are going to pass
mxArray *prhs[1]; // to store the input argument we are going to pass
prhs[0] = mxCreateString("fullpath"); // input argument to pass
if (mexCallMATLAB(nlhs, plhs, nrhs, prhs, "mfilename")) { // run mfilename('fullpath')
  mexErrMsgTxt("ctrlcCheckPoint(): mfilename('fullpath') returned error");
}
if (plhs == NULL) {
  mexErrMsgTxt("ctrlcCheckPoint(): mfilename('fullpath') returned NULL array of outputs");
}
if (plhs[0] == NULL) {
  mexErrMsgTxt("ctrlcCheckPoint(): mfilename('fullpath') returned NULL output instead of valid path");
}

// get full path to current function, including function's name
// (without the file extension)
char *pathAndName = mxArrayToString(plhs[0]);
if (pathAndName == NULL) {
  mexErrMsgTxt("ctrlcCheckPoint(): mfilename('fullpath') output cannot be converted to string");
}

// for some reason, using mexErrMsgTxt() to give this output
// doesn't work. Instead, we have to give the output to the
// standar error, and then call mexErrMsgTxt() to terminate
// execution of the program
std::cerr << "Operation terminated by user during "
    << "<a href=\"matlab:helpUtils.errorDocCallback('"
    << mexFunctionName()
    << "', '" << pathAndName << ".m', " << lineNumber << ")\">"
    << mexFunctionName()
    << "</a> (<a href=\"matlab:opentoline('"
    << sourceFile
    << "'," << lineNumber << ",0)\">line " << lineNumber
    << "</a>)"
    << std::endl;
mexErrMsgTxt("");
}

#define ctrlcCheckPoint(sourceFile, lineNumber)           \
if (utIsInterruptPending()) {                \
  ctrlcErrMsgTxt(sourceFile, lineNumber);           \
}


#endif /* MEXINTERRUPT_H */

Further reading

Read more in the tech topic.

Let's talk!

I'm Carlo Nicolini — I am interested on the reliability of AI reasoning systems (interpretability, inference-time methods, probabilistic language programming) and on quantitative portfolio optimization (I am a maintainer of skfolio). If you're working on something in these areas and think we might collaborate, chat, discuss, I'm happy to talk about it!

The best way to reach me is on via DM on LinkedIn.