Home > Articles

This chapter is from the book

34.3 Local Functors

So far our concerns have been largely syntactic. But there's a second, and more significant, troubling aspect of using STL algorithms, which represent a more serious imperfection. When you have a suitable function or functor available, then you can get code that is very succinct, as well as being powerful:

std::for_each(c.begin(), c.end(), fn());

However, as soon as you need to do something more sophisticated or specific to the elements in a range, you have two choices, neither of which is particularly attractive. Either you unroll the loop and provide the functionality yourself, or you wrap up that functionality in a custom function or functor.

34.3.1 Unrolled Loops

The normal option is to unroll the algorithm and do it longhand, as in the following code from an early version of the Arturius compiler multiplexer (see Appendix C):

Listing 34.1

void CoalesceOptions(. . .)
{
  . . .
  { OptsUsed_map_t::const_iterator  b = usedOptions.begin();
    OptsUsed_map_t::const_iterator  e = usedOptions.end();
  for(; b != e; ++b)
  {
    OptsUsed_map_t::value_type const &v = *b;
    if( !v.second &&
        v.first->bUseByDefault)
    {
      arcc_option option;
      option.name             = v.first->fullName;
      option.value            = v.first->defaultValue;
      option.bCompilerOption  = v.first->type == compiler;
      arguments.push_back(option);
    }
  }}
  . . .

Naturally, such things can get quite verbose; I could have grabbed another from the same file that was considerably longer.

34.3.2 Custom Functors

The alternative to unrolling loops is to write custom functors to provide the behavior you need. If the behavior is something that can be used in a variety of places that's great, but oftentimes you're writing a new class for just one, or a small number of cases.

Because it's a separate class, it will be physically separate from where it is being used, leading to the code being hard to comprehend and maintain. The best way you can handle this is to define the functor class in the compilation unit as the code that's going to be using it, preferably just before the function where it is used.

Listing 34.2

struct argument_saver
{
public:
  argument_saver(ArgumentsList &args)
    : m_args(args)
  {}
  void operator ()(OptsUsed_map_t::value_type const &o) const
  {
    if( !o.second &&
        o.first->bUseByDefault)
    {
      arcc_option option;
      . . .
      m_args.push_back(option);
    }
  }
private:
  ArgumentsList &m_args;
};
void CoalesceOptions(. . .)
{
  . . .
  std::for_each( usedOptions.begin(), usedOptions.end()
               , argument_saver(arguments));
  . . .

But the domain specific code encapsulated within the functor is physically separate from the only place(s) where it is used, and meaningful, and this is not ideal. The separation reduces maintainability and, worse, encourages overeager engineers to try and reuse it or to refactor it.

34.3.3 Nested Functors

One thing that could be done to improve the problem of separation of specific functors from their point of its use might be to allow them to be defined within the function in which they're used. For example, we might want argument_saver to be defined within fn(), as in:

 . . .

Alas, local functor classes are not legal in C++. This is because a template argument must refer to an entity with external linkage (C++-98: 14.3.2).

NOTE

Imperfection: C++ does not support local functor classes (to be used with template algorithms).

Notwithstanding the illegality, some compilers do allow them: CodeWarrior, Digital Mars, and Watcom. Furthermore, Borland and Visual C++ can be tricked into supporting it by the simple rouse of encapsulating the next class within another nested class, as in

Listing 34.3

void CoalesceOptions(. . .)
{
  . . .
  struct argument_saver
  {
    . . .
  };
  std::for_each( usedOptions.begin(), usedOptions.end()
               , argument_saver(arguments));
. . .
}

Table 34.1 summarizes the support for both forms for several popular compilers. Comeau, GCC, and Intel do not supported nested functions in either guise.

Table 34.1

Compiler

Local class

Nested local class

Borland

No

Yes

CodeWarrior

Yes

Yes

Comeau

No

No

Digital Mars

Yes

Yes

GCC

No

No

Intel

No

No

Visual C++

No

Yes

Watcom

Yes

Yes


If you're using only one or more of Borland, CodeWarrior, Digital Mars, Visual C++, and Watcom, then you might choose this approach. But it's not legal, and your portability will be compromised if you do so.

34.3.4 Legally Bland

The only legal way I know of to make such things work is turgid beyond bearing. Since the template's parameterizing type must be an external type, we define the function type outside the function. Given that we want to specialize the behavior in a local class, we must relate the internal and external classes. Since we cannot use templates, we can fall back on the old C++ workhorse: polymorphism. The local class argument_saver inherits from the external class argument_processor, and overrides its operator ()() const, as in:

Listing 34.4

struct argument_processor
{
public:
  virtual void operator ()(OptsUsed_map_t::value_type const &o) const = 0
};
void CoalesceOptions(. . .)
{
  . . .
  struct argument_saver
    : argument_processor
  {
    virtual void 
          operator ()(OptsUsed_map_t::value_type const &o) const
    {
      . . . 
    }
  };

That doesn't seem so bad, I suppose. However, to use it in a template algorithm requires that the parameterization be done in terms of the external (parent) class. And since the parent is an abstract class, the functor must be passed as a (const) reference, rather than by value.

Further, std::for_each() takes the functor type as the second template parameter, so it is necessary to explicitly stipulate the iterator type as well. Thus, using the "convenient" for_each() isn't very succinct, or general, and it certainly isn't pretty:

for_each< OptsUsed_map_t::const_iterator
        , argument_processor const &>( &ari[0], &ari[10]
                                     , argument_saver());

You'd have to agree that manual enumeration would be preferable. There's one last gasp at making this prettier, which is to define a for_each() equivalent that takes the template parameters in the reverse order so that the function type can be deduced implicitly:

template< typename F
        , typename I
        >
inline F for_each_l(I first, I last, F fn)
{
  return std::for_each<I, F>(first, last, fn);
}

which brings us to the final barely chewable form:

for_each_l<argument_processor const &>( &ari[0], &ari[10]
                                      , argument_saver());

But for my money this is still nowhere near good enough. Imagine the poor maintenance programmer trying to follow this stuff!

34.3.5 Generalized Functors: Type Tunnelling

If we can't make functors more local, maybe we can improve the situation by making them more general? Let's look at an example where we can expand the generality of the is_large functor (see section 34.1). We can use this with a sequence whose value_type is, or may be implicitly converted to, char const*, such as glob_sequence. Unfortunately, it can only be used with such types. If we want to use the same function with a type that uses Unicode encoding and the wchar_t type, it won't work.

One answer to this is to make is_large a template, parameterizable via its character type, as in:

template <typename C>
  : public std::unary_function<C const *, bool>
struct is_large
{
  bool operator ()(C const *file) const;
};

Now this will work with sequences using either char or wchar_t (using the fictional Unicode globw_sequence), so long as we stipulate the appropriate instantiation:

glob_sequence  gs("/usr/include/", "impcpp*");
n = std::count_if(gs.begin(), gs.end(), is_large<char>());
globw_sequence gsw(L"/usr/include/", L"impcpp*");
n = std::count_if(gsw.begin(), gsw.end(), is_large<wchar_t>());

That's a lot more useful, but it's still not the full picture. Looking back to section 20.6.3, we also looked at another file system enumeration sequence readdir_sequence, whose value_typestruct dirent const*—is not implicitly convertible to char const*. The solution for the problem in that section was to use Access Shims (see section 20.6.1), and we can apply them here to the same end. However, it's a little more complex now, because we've got templates involved, as shown in Listing 34.5.

Listing 34.5

template< typename C
        , typename A = C const *
        >
struct is_large
	: public std::unary_function<A, bool>
{
  template <typename S>
  bool operator ()(S const &file) const
  {

return is_large_(c_str_ptr(file)); // apply c_str_ptr shim

  }
private:
  static bool is_large_(C const *file)
  {
    . . . // determines whether large or not
  }
};

The function-call operator—operator ()() const—is now a template member function, which attempts to convert whatever type is applied to it via the c_str_ptr() shim to C const*, which is then passed to the static implementation method is_large_(). Now we can use the functor with any type for which a suitable c_str_ptr() definition exists and is visible, hence:

readdir_sequence  rs("/usr/include/");
n = std::count_if(rs.begin(), rs.end(), is_large<char>());

I call this mechanism Type Tunneling.

NOTE

Definition: Type Tunneling is a mechanism whereby two logically related but physically unrelated types can be made to interoperate via the use of Access Shims. The shim allows an external type to be tunneled through an interface and presented to the internal type in a recognized and compatible form.

I've used this mechanism to great effect throughout my work over the last few years. As well as facilitating the decoupled interoperation of a large spectrum of physically unrelated types via C-string forms, there is also the generalized manipulation of handles, pointers, and even synchronization objects. Type tunneling (and shims in general) goes to town on the principal of making the compiler one's batman. We saw another example of type tunneling in section 21.2, whereby virtually any COM-compatible type can be tunneled into the logging API through the combination of generic template constructors and InitialiseVariant() overloads, which combine to act as an access shim.

34.3.6 A Step too Far, Followed by a Sure Step Beyond

You may be wondering whether we can take this one further step, and remove the need to stipulate the character type. The answer is that we can, and with ease, as shown in Listing 34.6.

Listing 34.6

struct is_large
  : public std::unary_function<. . ., bool>
{
  template <typename S>
  bool operator ()(S const &file) const
  {
    return is_large_(c_str_ptr(file));
  }
private:
  static bool is_large_(char const *file);
  static bool is_large_(wchar_t const *file);
};

It is now simpler to use, as in:

n = std::count_if(rs.begin(), rs.end(), is_large());
n = std::count_if(gs.begin(), gs.end(), is_large());
n = std::count_if(gsw.begin(), gsw.end(), is_large());

However, there's a good reasons why we don't do this. This functor is a predicate—a functor whose function-call operator returns a Boolean result reflecting some aspect of its argument(s). One important aspect of predicates is that they may be combined with adaptors [Muss2001], as in the following statement that counts the number of small files:

n = std::count_if( gs.begin(), gs.end()
                 , std::not1(is_large<char>()));

In order for adaptors to work with predicates, they must be able to elicit the member types argument_type and result_type from the predicate class. This is normally done by deriving from std::unary_operator. Now we can see why the final refinement shown in Listing 34.6 cannot be done. There's no way to specify the argument type, other than to define the predicate class as a template with a single template parameter to define the predicate. But this would have to be provided in every use as there's no sensible default, which would be onerous to use and confusing to read.

This is why the actual functor definition is a two-parameter template, where the first parameter C represents the character type and the second parameter A, which defaults to C const*, represents the argument_type of the predicate.

template< typename C
        , typename A = C const *
        >
struct is_large
	: public std::unary_function<A, bool>
{
  . . .

Now when we want to use this with an adaptor and a sequence whose value_type is not C const*, we do something like the following:

n = std::count_if( rs.begin(), rs.end() // rs: readdir_sequence
            , std::not1(is_large<char, struct dirent const*>()));

It doesn't have beauty that will stop a man's heart, but it's bearable considering the fact that the combination of sequence and adaptor that necessitates it is low incidence, and the advantages of the generality and consequent reuse are high. It also facilitates a high degree of generality, since we can write a template algorithm that would be perfectly compatible with any sequence, and maintain the type tunneling, as in:

template< typename C // character type
        , typename S // sequence type
        >
void do_stuff(. . .)
{
  S s = . . .;
  size_t n = std::count_if( s.begin(), s.end()
            , std::not1(is_large<C, typename S::value_type>()));
  . . .

Okay, before you think I've gone completely cracked, I confess that this is hardly something that gambols into the brain through the merest wisp of rapidly clearing mental fog. It's something that you definitely have to think about. But there are times when we simply have to have complex bits of code; check out some of your friendly neighborhood open-source C++ libraries if you don't believe me.

The point is that we have a mechanism for writing highly generalized—reusable, in other words—components, which are very digestible—in that they follow accepted idiomatic forms—in most cases where they are used. This generality is bought for the acceptable, in my opinion, cost of requiring the specification of the given sequence's value_type when used with adaptors.

34.3.7 Local Functors and Callback APIs

Just because local functors are not allowed for STL algorithms does not mean that they cannot find use in enumeration. In fact, when dealing with callback enumeration APIs, local classes are eminently solutions. Consider the implementation of the function FindChild ById() (see Listing 34.7), which provides a deep-descendent equivalent to the well-known Win32 function GetDlgItem(). GetDlgItem() returns a handle to an immediate child window bearing the given id. FindChildById() provides the same functionality, but is able to locate the id in any descendent windows, not just immediate children.

Listing 34.7

HWND FindChildById(HWND hwndParent, int id)
{
  if(::GetDlgCtrlID(hwndParent) == id)
  {
    return hwndParent; // Searching for self
  }
  else
  {
    struct ChildFind
    {
      ChildFind(HWND hwndParent, int id)
        : m_hwndChild(NULL)
        , m_id(id)
      {
        // Enumerate, passing "this" as identity structure
        ::EnumChildWindows( hwndParent,
                            FindProc,
                            reinterpret_cast<LPARAM>(this));
      }
      static BOOL CALLBACK FindProc(HWND hwnd, LPARAM lParam)
      {
        ChildFind &find = *reinterpret_cast<ChildFind*>(lParam);
        return (::GetDlgCtrlID(hwnd) == find.m_id)
                  ? (find.m_hwndChild = hwnd, FALSE)
                  : TRUE;
      }
      HWND      m_hwndChild;
      int const m_id;
    } find(hwndParent, id);
    return find.m_hwndChild;
  }
}

The class ChildFind is declared within the function, maximizing encapsulation. The instance find is passed the parent window handle and the id to find. The constructor records the id in the m_id member, and sets the search result member m_hwndChild to NULL. It then calls the Win32 callback enumeration function EnumChildWindows(), which takes the parent window handle, a callback function, and a caller-supplied parameter. The instance passes the static method FindProc() and itself as the parameter. FindProc() then responds to each callback by determining whether the desired id has been located, and, if so, it records the handle and terminates the search.

When the construction of find is complete, it will either contain the requested handle, or NULL, in the m_hwndChild member. In either case this is returned to the caller of FindChildById(). The entire enumeration has been carried out in the constructor of the local class, whose definition is not accessible to any outside context. FindChildById() perfectly encapsulates the ChildFind class.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020